/*
 * Copyright 1995-2002 by Frederic Lepied, France. <Lepied@XFree86.org>
 * Copyright 2002-2013 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 "xf86Wacom.h"

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

#include <math.h>
#include <asm/types.h>
#include <linux/input.h>
#include <sys/utsname.h>

#define MAX_USB_EVENTS 128

typedef struct {
	unsigned int wcmLastToolSerial;
	int wcmDeviceType;
	Bool wcmPenTouch;
	Bool wcmUseMT;
	int wcmMTChannel;
	unsigned int wcmEventCnt;
	struct input_event wcmEvents[MAX_USB_EVENTS];
	uint32_t wcmEventFlags;      /* event types received in this frame */
	int nbuttons;                /* total number of buttons */
	int npadkeys;                /* number of pad keys in the above array */
	int padkey_code[WCM_MAX_BUTTONS];/* hardware codes for buttons */
	int lastChannel;
	Bool grabDevice;
} wcmUSBData;

static Bool usbDetect(WacomDevicePtr priv);
static Bool usbParseOptions(WacomDevicePtr priv);
static Bool usbWcmInit(WacomDevicePtr priv);
static int usbProbeKeys(WacomDevicePtr priv);
static int usbStart(WacomDevicePtr priv);
static int usbInitProtocol5(WacomDevicePtr priv);
static int usbInitProtocol4(WacomDevicePtr priv);
static int usbInitialize(WacomDevicePtr priv);
static int usbParse(WacomDevicePtr priv, const unsigned char* data, unsigned long len);
static int usbDetectConfig(WacomDevicePtr priv);
static void usbParseEvent(WacomDevicePtr priv,
	const struct input_event* event);
static void usbParseSynEvent(WacomDevicePtr priv,
			     const struct input_event *event);
static void usbParseMscEvent(WacomDevicePtr priv,
			     const struct input_event *event);
static void usbDispatchEvents(WacomDevicePtr priv);
static int usbChooseChannel(WacomCommonPtr common, int device_type, unsigned int serial);

static WacomHWClass gWacomUSBDevice =
{
	.Detect = usbDetect,
	.ProbeKeys = usbProbeKeys,
	.ParseOptions = usbParseOptions,
	.Init = usbWcmInit,
};
WacomHWClass *WacomGetClassUSB(void) { return &gWacomUSBDevice; }

#define DEFINE_MODEL(mname, identifier, protocol) \
static struct _WacomModel mname =		\
{						\
	.name = identifier,			\
	.Initialize = usbInitProtocol##protocol,\
	.DetectConfig = usbDetectConfig,	\
	.Start = usbStart,			\
	.Parse = usbParse,			\
}

DEFINE_MODEL(usbUnknown,	"Unknown USB",		5);
DEFINE_MODEL(usbPenPartner,	"USB PenPartner",	4);
DEFINE_MODEL(usbGraphire,	"USB Graphire",		4);
DEFINE_MODEL(usbGraphire2,	"USB Graphire2",	4);
DEFINE_MODEL(usbGraphire3,	"USB Graphire3",	4);
DEFINE_MODEL(usbGraphire4,	"USB Graphire4",	4);
DEFINE_MODEL(usbBamboo,		"USB Bamboo",		4);
DEFINE_MODEL(usbBamboo1,	"USB Bamboo1",		4);
DEFINE_MODEL(usbBambooFun,	"USB BambooFun",	4);
DEFINE_MODEL(usbCintiq,		"USB PL/Cintiq/DTU",	4);
DEFINE_MODEL(usbCintiqPartner,	"USB CintiqPartner",	4);
DEFINE_MODEL(usbIntuos,		"USB Intuos1",		5);
DEFINE_MODEL(usbIntuos2,	"USB Intuos2",		5);
DEFINE_MODEL(usbIntuos3,	"USB Intuos3",		5);
DEFINE_MODEL(usbIntuos4,	"USB Intuos4",		5);
DEFINE_MODEL(usbIntuos5,	"USB Intuos5",		5);
DEFINE_MODEL(usbIntuosPro,	"USB Intuos Pro",	5);
DEFINE_MODEL(usbVolito,		"USB Volito",		4);
DEFINE_MODEL(usbVolito2,	"USB Volito2",		4);
DEFINE_MODEL(usbCintiqV5,	"USB CintiqV5",		5);
DEFINE_MODEL(usbTabletPC,	"USB TabletPC",		4);

/*****************************************************************************
 * usbDetect --
 *   Test if the attached device is USB.
 ****************************************************************************/

static Bool usbDetect(WacomDevicePtr priv)
{
	int version;
	int err;
#ifdef DEBUG
	DBG(1, priv, "\n");
#endif

	SYSCALL(err = ioctl(wcmGetFd(priv), EVIOCGVERSION, &version));

	if (err < 0)
	{
		wcmLog(priv, W_ERROR, "usbDetect: can not ioctl version\n");
		return 0;
	}

	return 1;
}

static Bool usbParseOptions(WacomDevicePtr priv)
{
	WacomCommonPtr common = priv->common;
	wcmUSBData *usbdata;

	if (!common->private &&
	    !(common->private = calloc(1, sizeof(wcmUSBData))))
	{
		wcmLog(priv, W_ERROR, "unable to alloc event queue.\n");
		return FALSE;
	}

	usbdata = common->private;
	usbdata->grabDevice = wcmOptCheckBool(priv, "GrabDevice", FALSE);

	return TRUE;
}

/*****************************************************************************
 * usbStart --
 ****************************************************************************/
static int
usbStart(WacomDevicePtr priv)
{
	WacomCommonPtr common = priv->common;
	wcmUSBData *usbdata = common->private;
	int err;

	if (usbdata->grabDevice)
	{
		/* Try to grab the event device so that data don't leak to /dev/input/mice */
		SYSCALL(err = ioctl(wcmGetFd(priv), EVIOCGRAB, (pointer)1));

		/* this is called for all tools, so all but the first one fails with
		 * EBUSY */
		if (err < 0 && errno != EBUSY)
			wcmLog(priv, W_ERROR,
				    "Wacom X driver can't grab event device (%s)\n",
				    strerror(errno));
	}
	return Success;
}

/* Key codes used to mark tablet buttons -- must be in sync
 * with the keycode array in wacom kernel drivers.
 */
static unsigned short padkey_codes [] = {
	BTN_0, BTN_1, BTN_2, BTN_3, BTN_4,
	BTN_5, BTN_6, BTN_7, BTN_8, BTN_9,
	BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z,
	BTN_BASE, BTN_BASE2, BTN_BASE3,
	BTN_BASE4, BTN_BASE5, BTN_BASE6,
	BTN_TL, BTN_TR, BTN_TL2, BTN_TR2, BTN_SELECT
};

/* Fixed mapped stylus and mouse buttons */

#define WCM_USB_MAX_MOUSE_BUTTONS 5
#define WCM_USB_MAX_STYLUS_BUTTONS 4

static unsigned short mouse_codes [] = {
	BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, BTN_BACK, BTN_FORWARD,
	BTN_SIDE, BTN_EXTRA
};

static struct WacomModelDesc
{
	const unsigned int vendor_id;
	const unsigned int model_id;
	int yRes; /* tablet Y resolution in units/meter */
	int xRes; /* tablet X resolution in units/meter */
	WacomModelPtr model;
	const char *name;
} WacomModelDesc [] =
{
	{ WACOM_VENDOR_ID, 0x00,  39370,  39370, &usbPenPartner, "PenPartner"		},
	{ WACOM_VENDOR_ID, 0x10,  80000,  80000, &usbGraphire,   "Graphire"		},
	{ WACOM_VENDOR_ID, 0x11,  80000,  80000, &usbGraphire2,  "Graphire2 4x5"	},
	{ WACOM_VENDOR_ID, 0x12,  80000,  80000, &usbGraphire2,  "Graphire2 5x7"	},
	{ WACOM_VENDOR_ID, 0x13,  80000,  80000, &usbGraphire3,  "Graphire3 4x5"	},
	{ WACOM_VENDOR_ID, 0x14,  80000,  80000, &usbGraphire3,  "Graphire3 6x8"	},
	{ WACOM_VENDOR_ID, 0x15,  80000,  80000, &usbGraphire4,  "Graphire4 4x5"	},
	{ WACOM_VENDOR_ID, 0x16,  80000,  80000, &usbGraphire4,  "Graphire4 6x8"	},
	{ WACOM_VENDOR_ID, 0x17, 100000, 100000, &usbBambooFun,  "BambooFun 4x5"	},
	{ WACOM_VENDOR_ID, 0x18, 100000, 100000, &usbBambooFun,  "BambooFun 6x8"	},
	{ WACOM_VENDOR_ID, 0x19,  80000,  80000, &usbBamboo1,    "Bamboo1 Medium"	},
	{ WACOM_VENDOR_ID, 0x81,  80000,  80000, &usbGraphire4,  "Graphire4 6x8 BlueTooth" },

	{ WACOM_VENDOR_ID, 0xD1, 100000, 100000, &usbBamboo,     "CTL-460"		},
	{ WACOM_VENDOR_ID, 0xD4, 100000, 100000, &usbBamboo,     "CTH-461"		},
	{ WACOM_VENDOR_ID, 0xD3, 100000, 100000, &usbBamboo,     "CTL-660"		},
	{ WACOM_VENDOR_ID, 0xD2, 100000, 100000, &usbBamboo,     "CTL-461/S"		},
	{ WACOM_VENDOR_ID, 0xD0, 100000, 100000, &usbBamboo,     "Bamboo Touch"		},
	{ WACOM_VENDOR_ID, 0xD6, 100000, 100000, &usbBamboo,     "CTH-460/K"		},
	{ WACOM_VENDOR_ID, 0xD7, 100000, 100000, &usbBamboo,     "CTH-461/S"		},
	{ WACOM_VENDOR_ID, 0xD8, 100000, 100000, &usbBamboo,     "CTH-661/S1"		},
	{ WACOM_VENDOR_ID, 0xDA, 100000, 100000, &usbBamboo,     "CTH-461/L"		},
	{ WACOM_VENDOR_ID, 0xDB, 100000, 100000, &usbBamboo,     "CTH-661/L"		},

	{ WACOM_VENDOR_ID, 0x20, 100000, 100000, &usbIntuos,     "Intuos 4x5"		},
	{ WACOM_VENDOR_ID, 0x21, 100000, 100000, &usbIntuos,     "Intuos 6x8"		},
	{ WACOM_VENDOR_ID, 0x22, 100000, 100000, &usbIntuos,     "Intuos 9x12"		},
	{ WACOM_VENDOR_ID, 0x23, 100000, 100000, &usbIntuos,     "Intuos 12x12"		},
	{ WACOM_VENDOR_ID, 0x24, 100000, 100000, &usbIntuos,     "Intuos 12x18"		},

	{ WACOM_VENDOR_ID, 0x03,  20000,  20000, &usbCintiqPartner, "PTU600"		},

	{ WACOM_VENDOR_ID, 0x30,  20000,  20000, &usbCintiq,     "PL400"		},
	{ WACOM_VENDOR_ID, 0x31,  20000,  20000, &usbCintiq,     "PL500"		},
	{ WACOM_VENDOR_ID, 0x32,  20000,  20000, &usbCintiq,     "PL600"		},
	{ WACOM_VENDOR_ID, 0x33,  20000,  20000, &usbCintiq,     "PL600SX"		},
	{ WACOM_VENDOR_ID, 0x34,  20000,  20000, &usbCintiq,     "PL550"		},
	{ WACOM_VENDOR_ID, 0x35,  20000,  20000, &usbCintiq,     "PL800"		},
	{ WACOM_VENDOR_ID, 0x37,  20000,  20000, &usbCintiq,     "PL700"		},
	{ WACOM_VENDOR_ID, 0x38,  20000,  20000, &usbCintiq,     "PL510"		},
	{ WACOM_VENDOR_ID, 0x39,  20000,  20000, &usbCintiq,     "PL710"		},
	{ WACOM_VENDOR_ID, 0x3A,  20000,  20000, &usbCintiq,     "DTI520"		},
	{ WACOM_VENDOR_ID, 0xC0,  20000,  20000, &usbCintiq,     "DTF720"		},
	{ WACOM_VENDOR_ID, 0xC2,  20000,  20000, &usbCintiq,     "DTF720a"		},
	{ WACOM_VENDOR_ID, 0xC4,  20000,  20000, &usbCintiq,     "DTF521"		},
	{ WACOM_VENDOR_ID, 0xC7, 100000, 100000, &usbCintiq,     "DTU1931"		},
	{ WACOM_VENDOR_ID, 0xCE, 100000, 100000, &usbCintiq,     "DTU2231"		},
	{ WACOM_VENDOR_ID, 0xF0, 100000, 100000, &usbCintiq,     "DTU1631"		},
	{ WACOM_VENDOR_ID, 0x35a, 100000, 100000, &usbCintiq,    "DTH1152"              },
	{ WACOM_VENDOR_ID, 0x368, 100000, 100000, &usbCintiq,    "DTH1152"              }, /* Touch */
	{ WACOM_VENDOR_ID, 0x382, 100000, 100000, &usbCintiq,    "DTK2451"              },
	{ WACOM_VENDOR_ID, 0x37D, 100000, 100000, &usbCintiq,    "DTH2452"              },
	{ WACOM_VENDOR_ID, 0x37E, 100000, 100000, &usbCintiq,    "DTH2452"              }, /* Touch */
	{ WACOM_VENDOR_ID, 0x3A6, 100000, 100000, &usbCintiq,    "Wacom One Pen Display 13"	},

	{ WACOM_VENDOR_ID, 0x41, 100000, 100000, &usbIntuos2,    "Intuos2 4x5"		},
	{ WACOM_VENDOR_ID, 0x42, 100000, 100000, &usbIntuos2,    "Intuos2 6x8"		},
	{ WACOM_VENDOR_ID, 0x43, 100000, 100000, &usbIntuos2,    "Intuos2 9x12"		},
	{ WACOM_VENDOR_ID, 0x44, 100000, 100000, &usbIntuos2,    "Intuos2 12x12"	},
	{ WACOM_VENDOR_ID, 0x45, 100000, 100000, &usbIntuos2,    "Intuos2 12x18"	},
	{ WACOM_VENDOR_ID, 0x47, 100000, 100000, &usbIntuos2,    "Intuos2 6x8 "		},

	{ WACOM_VENDOR_ID, 0x60,  50000,  50000, &usbVolito,     "Volito"		},

	{ WACOM_VENDOR_ID, 0x61,  50000,  50000, &usbVolito2,    "PenStation"		},
	{ WACOM_VENDOR_ID, 0x62,  50000,  50000, &usbVolito2,    "Volito2 4x5"		},
	{ WACOM_VENDOR_ID, 0x63,  50000,  50000, &usbVolito2,    "Volito2 2x3"		},
	{ WACOM_VENDOR_ID, 0x64,  50000,  50000, &usbVolito2,    "PenPartner2"		},

	{ WACOM_VENDOR_ID, 0x65, 100000, 100000, &usbBamboo,     "Bamboo"		},
	{ WACOM_VENDOR_ID, 0x69,  39842,  39842, &usbBamboo1,    "Bamboo1"		},
	{ WACOM_VENDOR_ID, 0x6A, 100000, 100000, &usbBamboo1,    "Bamboo1 4x6"		},
	{ WACOM_VENDOR_ID, 0x6B, 100000, 100000, &usbBamboo1,    "Bamboo1 5x8"		},

	{ WACOM_VENDOR_ID, 0xB0, 200000, 200000, &usbIntuos3,    "Intuos3 4x5"		},
	{ WACOM_VENDOR_ID, 0xB1, 200000, 200000, &usbIntuos3,    "Intuos3 6x8"		},
	{ WACOM_VENDOR_ID, 0xB2, 200000, 200000, &usbIntuos3,    "Intuos3 9x12"		},
	{ WACOM_VENDOR_ID, 0xB3, 200000, 200000, &usbIntuos3,    "Intuos3 12x12"	},
	{ WACOM_VENDOR_ID, 0xB4, 200000, 200000, &usbIntuos3,    "Intuos3 12x19"	},
	{ WACOM_VENDOR_ID, 0xB5, 200000, 200000, &usbIntuos3,    "Intuos3 6x11"		},
	{ WACOM_VENDOR_ID, 0xB7, 200000, 200000, &usbIntuos3,    "Intuos3 4x6"		},

	{ WACOM_VENDOR_ID, 0xB8, 200000, 200000, &usbIntuos4,    "Intuos4 4x6"		},
	{ WACOM_VENDOR_ID, 0xB9, 200000, 200000, &usbIntuos4,    "Intuos4 6x9"		},
	{ WACOM_VENDOR_ID, 0xBA, 200000, 200000, &usbIntuos4,    "Intuos4 8x13"		},
	{ WACOM_VENDOR_ID, 0xBB, 200000, 200000, &usbIntuos4,    "Intuos4 12x19"	},
	{ WACOM_VENDOR_ID, 0xBC, 200000, 200000, &usbIntuos4,    "Intuos4 WL USB Endpoint"	},
	{ WACOM_VENDOR_ID, 0xBD, 200000, 200000, &usbIntuos4,    "Intuos4 WL Bluetooth Endpoint"},

	{ WACOM_VENDOR_ID, 0x26, 200000, 200000, &usbIntuos5,    "Intuos5 touch S"	},
	{ WACOM_VENDOR_ID, 0x27, 200000, 200000, &usbIntuos5,    "Intuos5 touch M"	},
	{ WACOM_VENDOR_ID, 0x28, 200000, 200000, &usbIntuos5,    "Intuos5 touch L"	},
	{ WACOM_VENDOR_ID, 0x29, 200000, 200000, &usbIntuos5,    "Intuos5 S"		},
	{ WACOM_VENDOR_ID, 0x2A, 200000, 200000, &usbIntuos5,    "Intuos5 M"		},
	{ WACOM_VENDOR_ID, 0x314,200000, 200000, &usbIntuosPro,  "Intuos Pro S"		},
	{ WACOM_VENDOR_ID, 0x315,200000, 200000, &usbIntuosPro,  "Intuos Pro M"		},
	{ WACOM_VENDOR_ID, 0x317,200000, 200000, &usbIntuosPro,  "Intuos Pro L"		},

	{ WACOM_VENDOR_ID, 0x3F, 200000, 200000, &usbCintiqV5,   "Cintiq 21UX"		},
	{ WACOM_VENDOR_ID, 0xC5, 200000, 200000, &usbCintiqV5,   "Cintiq 20WSX"		},
	{ WACOM_VENDOR_ID, 0xC6, 200000, 200000, &usbCintiqV5,   "Cintiq 12WX"		},
	{ WACOM_VENDOR_ID, 0xCC, 200000, 200000, &usbCintiqV5,   "Cintiq 21UX2"		},
	{ WACOM_VENDOR_ID, 0xF4, 200000, 200000, &usbCintiqV5,   "Cintiq 24HD"		},
	{ WACOM_VENDOR_ID, 0xFA, 200000, 200000, &usbCintiqV5,   "Cintiq 22HD"		},
	{ WACOM_VENDOR_ID, 0xF8, 200000, 200000, &usbCintiqV5,   "Cintiq 24HD touch (EMR digitizer)" },
	{ WACOM_VENDOR_ID, 0x304,200000, 200000, &usbCintiqV5,   "Cintiq 13HD"		},
	{ WACOM_VENDOR_ID, 0x57, 200000, 200000, &usbCintiqV5,   "DTK2241"		},
	{ WACOM_VENDOR_ID, 0x59, 200000, 200000, &usbCintiqV5,   "DTH2242"		},
	{ WACOM_VENDOR_ID, 0x5B, 200000, 200000, &usbCintiqV5,   "Cintiq 22HDT"		},
	{ WACOM_VENDOR_ID, 0x32B, 200000, 200000, &usbCintiqV5,  "Cintiq 27QHDT"	},
	{ WACOM_VENDOR_ID, 0x32C, 200000, 200000, &usbCintiqV5,  "Cintiq 27QHDT"	}, /* Touch */
	{ WACOM_VENDOR_ID, 0x34A, 200000, 200000, &usbCintiqV5,  "Mobilestudio Pro 13"	}, /* Touch */
	{ WACOM_VENDOR_ID, 0x34B, 200000, 200000, &usbCintiqV5,  "MobileStudio Pro 16"	}, /* Touch */
	{ WACOM_VENDOR_ID, 0x34D, 200000, 200000, &usbCintiqV5,  "MobileStudio Pro 13"	},
	{ WACOM_VENDOR_ID, 0x34E, 200000, 200000, &usbCintiqV5,  "MobileStudio Pro 16"	},
	{ WACOM_VENDOR_ID, 0x34F, 200000, 200000, &usbCintiqV5,  "Cintiq 13 FHD Pro"	},
	{ WACOM_VENDOR_ID, 0x350, 200000, 200000, &usbCintiqV5,  "Cintiq 16 UHD Pro"	},
	{ WACOM_VENDOR_ID, 0x351, 200000, 200000, &usbCintiqV5,  "Cintiq Pro 24"	},
	{ WACOM_VENDOR_ID, 0x352, 200000, 200000, &usbCintiqV5,  "Cintiq Pro 32"	},
	{ WACOM_VENDOR_ID, 0x353, 200000, 200000, &usbCintiqV5,  "Cintiq 13 FHD Pro"	}, /* Touch */
	{ WACOM_VENDOR_ID, 0x354, 200000, 200000, &usbCintiqV5,  "Cintiq 16 UHD Pro"	}, /* Touch */
	{ WACOM_VENDOR_ID, 0x355, 200000, 200000, &usbCintiqV5,  "Cintiq Pro 24"	}, /* Touch */
	{ WACOM_VENDOR_ID, 0x356, 200000, 200000, &usbCintiqV5,  "Cintiq Pro 32"	}, /* Touch */
	{ WACOM_VENDOR_ID, 0x37C, 200000, 200000, &usbCintiqV5,  "Cintiq Pro 24"	}, /* Pen-only model */
	{ WACOM_VENDOR_ID, 0x398, 200000, 200000, &usbCintiqV5,  "Mobilestudio Pro 13"	},
	{ WACOM_VENDOR_ID, 0x399, 200000, 200000, &usbCintiqV5,  "MobileStudio Pro 16"	},
	{ WACOM_VENDOR_ID, 0x39A, 200000, 200000, &usbCintiqV5,  "MobileStudio Pro 13"	}, /* Touch */
	{ WACOM_VENDOR_ID, 0x39B, 200000, 200000, &usbCintiqV5,  "MobileStudio Pro 16"	}, /* Touch */
	{ WACOM_VENDOR_ID, 0x3AA, 200000, 200000, &usbCintiqV5,  "MobileStudio Pro 16"	},
	{ WACOM_VENDOR_ID, 0x3AC, 200000, 200000, &usbCintiqV5,  "MobileStudio Pro 16"	}, /* Touch */

	{ WACOM_VENDOR_ID, 0x90, 100000, 100000, &usbTabletPC,   "TabletPC 0x90"	},
	{ WACOM_VENDOR_ID, 0x93, 100000, 100000, &usbTabletPC,   "TabletPC 0x93"	},
	{ WACOM_VENDOR_ID, 0x97, 100000, 100000, &usbTabletPC,   "TabletPC 0x97"	},
	{ WACOM_VENDOR_ID, 0x9A, 100000, 100000, &usbTabletPC,   "TabletPC 0x9A"	},
	{ WACOM_VENDOR_ID, 0x9F, 100000, 100000, &usbTabletPC,   "CapPlus  0x9F"	},
	{ WACOM_VENDOR_ID, 0xE2, 100000, 100000, &usbTabletPC,   "TabletPC 0xE2"	},
	{ WACOM_VENDOR_ID, 0xE3, 100000, 100000, &usbTabletPC,   "TabletPC 0xE3"	},
	{ WACOM_VENDOR_ID, 0xE5, 100000, 100000, &usbTabletPC,   "TabletPC 0xE5"	},
	{ WACOM_VENDOR_ID, 0xE6, 100000, 100000, &usbTabletPC,   "TabletPC 0xE6"	},
	{ WACOM_VENDOR_ID, 0xEC, 100000, 100000, &usbTabletPC,   "TabletPC 0xEC"	},
	{ WACOM_VENDOR_ID, 0xED, 100000, 100000, &usbTabletPC,   "TabletPC 0xED"	},
	{ WACOM_VENDOR_ID, 0xEF, 100000, 100000, &usbTabletPC,   "TabletPC 0xEF"	},
	{ WACOM_VENDOR_ID, 0x100,100000, 100000, &usbTabletPC,   "TabletPC 0x100"	},
	{ WACOM_VENDOR_ID, 0x101,100000, 100000, &usbTabletPC,   "TabletPC 0x101"	},
	{ WACOM_VENDOR_ID, 0x10D,100000, 100000, &usbTabletPC,   "TabletPC 0x10D"	},
	{ WACOM_VENDOR_ID, 0x116,100000, 100000, &usbTabletPC,   "TabletPC 0x116"	},
	{ WACOM_VENDOR_ID, 0x12C,100000, 100000, &usbTabletPC,   "TabletPC 0x12C"	},
	{ WACOM_VENDOR_ID, 0x4001,100000, 100000, &usbTabletPC,  "TabletPC 0x4001"	},
	{ WACOM_VENDOR_ID, 0x4004,100000, 100000, &usbTabletPC,  "TabletPC 0x4004"	},
	{ WACOM_VENDOR_ID, 0x5000,100000, 100000, &usbTabletPC,  "TabletPC 0x5000"	},
	{ WACOM_VENDOR_ID, 0x5002,100000, 100000, &usbTabletPC,  "TabletPC 0x5002"	},

	/* IDs from Waltop's driver, available http://www.waltop.com.tw/download.asp?lv=0&id=2.
	   Accessed 8 Apr 2010, driver release date 2009/08/11, fork of linuxwacom 0.8.4.
	   Some more info would be nice for the ID's below... */
	{ WALTOP_VENDOR_ID, 0x24,  80000,  80000, &usbGraphire,   "usb:172f:0024"	},
	{ WALTOP_VENDOR_ID, 0x25,  80000,  80000, &usbGraphire2,  "usb:172f:0025"	},
	{ WALTOP_VENDOR_ID, 0x26,  80000,  80000, &usbGraphire2,  "usb:172f:0026"	},
	{ WALTOP_VENDOR_ID, 0x27,  80000,  80000, &usbGraphire3,  "usb:172f:0027"	},
	{ WALTOP_VENDOR_ID, 0x28,  80000,  80000, &usbGraphire3,  "usb:172f:0028"	},
	{ WALTOP_VENDOR_ID, 0x30,  80000,  80000, &usbGraphire4,  "usb:172f:0030"	},
	{ WALTOP_VENDOR_ID, 0x31,  80000,  80000, &usbGraphire4,  "usb:172f:0031"	},
	{ WALTOP_VENDOR_ID, 0x32, 100000, 100000, &usbBambooFun,  "usb:172f:0032"	},
	{ WALTOP_VENDOR_ID, 0x33, 100000, 100000, &usbBambooFun,  "usb:172f:0033"	},
	{ WALTOP_VENDOR_ID, 0x34,  80000,  80000, &usbBamboo1,    "usb:172f:0034"	},
	{ WALTOP_VENDOR_ID, 0x35,  80000,  80000, &usbGraphire4,  "usb:172f:0035"	},
	{ WALTOP_VENDOR_ID, 0x36,  80000,  80000, &usbGraphire4,  "usb:172f:0036"	},
	{ WALTOP_VENDOR_ID, 0x37,  80000,  80000, &usbGraphire4,  "usb:172f:0037"	},
	{ WALTOP_VENDOR_ID, 0x38, 100000, 100000, &usbBambooFun,  "usb:172f:0038"	},
	{ WALTOP_VENDOR_ID, 0x39, 100000, 100000, &usbBambooFun,  "usb:172f:0039"	},
	{ WALTOP_VENDOR_ID, 0x51, 100000, 100000, &usbBamboo,     "usb:172f:0051"	},
	{ WALTOP_VENDOR_ID, 0x52, 100000, 100000, &usbBamboo,     "usb:172f:0052"	},

	{ WALTOP_VENDOR_ID, 0x53,  100000, 100000, &usbBamboo,    "usb:172f:0053"	},
	{ WALTOP_VENDOR_ID, 0x54,  100000, 100000, &usbBamboo,    "usb:172f:0054"	},
	{ WALTOP_VENDOR_ID, 0x55,  100000, 100000, &usbBamboo,    "usb:172f:0055"	},
	{ WALTOP_VENDOR_ID, 0x56,  100000, 100000, &usbBamboo,    "usb:172f:0056"	},
	{ WALTOP_VENDOR_ID, 0x57,  100000, 100000, &usbBamboo,    "usb:172f:0057"	},
	{ WALTOP_VENDOR_ID, 0x58,  100000, 100000, &usbBamboo,    "usb:172f:0058"	},
	{ WALTOP_VENDOR_ID, 0x500, 100000, 100000, &usbBamboo,    "usb:172f:0500"	},
	{ WALTOP_VENDOR_ID, 0x501, 100000, 100000, &usbBamboo,    "usb:172f:0501"	},
	{ WALTOP_VENDOR_ID, 0x502, 200000, 200000, &usbIntuos4,   "usb:172f:0502"	},
	{ WALTOP_VENDOR_ID, 0x503, 200000, 200000, &usbIntuos4,   "usb:172f:0503"	},

	/* N-Trig devices */
	{ NTRIG_VENDOR_ID,  0x01, 44173, 36772, &usbTabletPC,     "usb:1b96:0001"	},

	/* Add in Lenovo W700 Palmrest digitizer */
	{ LENOVO_VENDOR_ID, 0x6004, 100000, 100000, &usbTabletPC, "usb:17ef:6004"	} /* Pen-only */
};

size_t wcmListModels(const char **names, size_t len)
{
	for (size_t i = 0; i < min(len, ARRAY_SIZE(WacomModelDesc)); i++)
	{
		struct WacomModelDesc *m = &WacomModelDesc[i];
		names[i] = m->name;
	}
	return ARRAY_SIZE(WacomModelDesc);
}

static Bool usbWcmInit(WacomDevicePtr priv)
{
	struct input_id sID;
	WacomCommonPtr common = priv->common;
	wcmUSBData *usbdata;

	DBG(1, priv, "initializing USB tablet\n");

	/* fetch vendor, product, and model name */
	if (ioctl(wcmGetFd(priv), EVIOCGID, &sID) == -1) {
		wcmLog(priv, W_ERROR, "failed to ioctl ID .\n");
		return !Success;
	}

	if (!common->private &&
	    !(common->private = calloc(1, sizeof(wcmUSBData))))
	{
		wcmLog(priv, W_ERROR, "unable to alloc event queue.\n");
		return !Success;
	}

	usbdata = common->private;

	for (size_t i = 0; i < ARRAY_SIZE(WacomModelDesc); i++)
	{
		if (sID.vendor == WacomModelDesc[i].vendor_id &&
		    sID.product == WacomModelDesc [i].model_id)
		{
			common->wcmModel = WacomModelDesc [i].model;
			common->wcmResolX = WacomModelDesc [i].xRes;
			common->wcmResolY = WacomModelDesc [i].yRes;
		}
	}

	if (!common->wcmModel)
	{
		common->wcmModel = &usbUnknown;
		common->wcmResolX = common->wcmResolY = 1016;
	}

	/* Mark protocol 5 devices which predate the use of Intuos5 tech
	 * as using legacy IDs. These legacy IDs do not conform to the
	 * current format, in particular with respect to the use of the
	 * 0th device ID bit signalling EMR/AES.
	 */
	if (common->wcmModel == &usbIntuos || common->wcmModel == &usbIntuos2 ||
	    common->wcmModel == &usbIntuos3 || common->wcmModel == &usbIntuos4 ||
	    (sID.vendor == WACOM_VENDOR_ID && sID.product == 0x3F) || // Cintiq 21UX (Intuos3-era)
	    (sID.vendor == WACOM_VENDOR_ID && sID.product == 0xC5) || // Cintiq 20WSX (Intuos3-era)
	    (sID.vendor == WACOM_VENDOR_ID && sID.product == 0xC6) || // Cintiq 12WX (Intuos3-era)
	    (sID.vendor == WACOM_VENDOR_ID && sID.product == 0xCC))   // Cintiq 21UX2 (Intuos4-era)
	{
		TabletSetFeature(common, WCM_LEGACY_IDS);
	}

	/* Find out supported button codes. */
	usbdata->npadkeys = 0;
	for (size_t i = 0; i < ARRAY_SIZE(padkey_codes); i++)
		if (ISBITSET (common->wcmKeys, padkey_codes [i]))
			usbdata->padkey_code [usbdata->npadkeys++] = padkey_codes [i];

	if (usbdata->npadkeys == 0) {
		/* If no pad keys were detected, entertain the possibility that any
		 * mouse buttons which exist may belong to the pad (e.g. Graphire4).
		 * If we're wrong, this will over-state the capabilities of the pad
		 * but that shouldn't actually cause problems.
		 */
		for (size_t i = ARRAY_SIZE(mouse_codes) - 1; i > 0; i--) {
			if (ISBITSET(common->wcmKeys, mouse_codes[i])) {
				usbdata->npadkeys = WCM_USB_MAX_MOUSE_BUTTONS;
				break;
			}
		}
	}

	/* nbuttons tracks maximum buttons on all tools (stylus/mouse).
	 *
	 * Mouse support left, middle, right, side, and extra side button.
	 * Stylus support tip and 3 stylus buttons.
	 */
	if (ISBITSET (common->wcmKeys, BTN_TOOL_MOUSE))
		usbdata->nbuttons = WCM_USB_MAX_MOUSE_BUTTONS;
	else
		usbdata->nbuttons = WCM_USB_MAX_STYLUS_BUTTONS;

	return Success;
}

static int usbInitProtocol5(WacomDevicePtr priv)
{
	WacomCommonPtr common = priv->common;

	common->wcmProtocolLevel = WCM_PROTOCOL_5;
	common->wcmPktLength = sizeof(struct input_event);
	common->wcmProxoutDistDefault = PROXOUT_INTUOS_DISTANCE;

	/* tilt enabled */
	common->wcmFlags |= TILT_ENABLED_FLAG;

	return usbInitialize(priv);
}

static int usbInitProtocol4(WacomDevicePtr priv)
{
	WacomCommonPtr common = priv->common;

	common->wcmProtocolLevel = WCM_PROTOCOL_4;
	common->wcmPktLength = sizeof(struct input_event);
	common->wcmProxoutDistDefault = PROXOUT_GRAPHIRE_DISTANCE;

	/* tilt disabled */
	common->wcmFlags &= ~TILT_ENABLED_FLAG;

	return usbInitialize(priv);
}

/* Initialize fixed PAD channel's state to in proximity.
 *
 * Some, but not all, Wacom protocol 4/5 devices are always in proximity.
 * Because of evdev filtering, there will never be a BTN_TOOL_FINGER
 * sent to initialize state.
 * Generic protocol devices never send anything to help initialize PAD
 * device as well.
 * This helps those 2 cases and does not hurt the cases where kernel
 * driver sends out-of-proximity event for PAD since PAD is always on
 * its own channel, PAD_CHANNEL.
 */
static void usbWcmInitPadState(WacomDevicePtr priv)
{
	WacomCommonPtr common = priv->common;
	WacomDeviceState *ds;
	int channel = PAD_CHANNEL;

	DBG(6, common, "Initializing PAD channel %d\n", channel);

	ds = &common->wcmChannel[channel].work;

	ds->proximity = 1;
	ds->device_type = PAD_ID;
	ds->device_id = PAD_DEVICE_ID;
	ds->serial_num = channel;
}

int usbInitialize(WacomDevicePtr priv)
{
	struct input_absinfo absinfo;
	unsigned long ev[NBITS(EV_MAX)] = {0};
	unsigned long abs[NBITS(ABS_MAX)] = {0};
	unsigned long sw[NBITS(SW_MAX)] = {0};
	WacomCommonPtr common =	priv->common;
	wcmUSBData* private = common->private;
	int is_touch = IsTouch(priv);

	/* Devices such as Bamboo P&T may have Pad data reported in the same
	 * packet as Touch.  It's normal for Pad to be called first but logic
	 * requires it to act the same as Touch.
	 */
	if (ISBITSET(common->wcmKeys, BTN_TOOL_DOUBLETAP)
	     && ISBITSET(common->wcmKeys, BTN_FORWARD))
		is_touch = 1;

	if (ioctl(wcmGetFd(priv), EVIOCGBIT(0 /*EV*/, sizeof(ev)), ev) < 0)
	{
		wcmLog(priv, W_ERROR, "unable to ioctl event bits.\n");
		return !Success;
	}

	if (!ISBITSET(ev,EV_ABS))
	{
		/* may be an expresskey only interface */
		if (ISBITSET(common->wcmKeys, BTN_FORWARD) ||
		    ISBITSET(common->wcmKeys, BTN_0))
			goto pad_init;

		wcmLog(priv, W_ERROR, "no abs bits.\n");
		return !Success;
	}

	/* absolute values */
        if (ioctl(wcmGetFd(priv), EVIOCGBIT(EV_ABS, sizeof(abs)), abs) < 0)
	{
		wcmLog(priv, W_ERROR, "unable to ioctl max values.\n");
		return !Success;
	}

	/* max x */
	if (ioctl(wcmGetFd(priv), EVIOCGABS(ABS_X), &absinfo) < 0)
	{
		/* may be a PAD only interface */
		if (ISBITSET(common->wcmKeys, BTN_FORWARD) ||
		    ISBITSET(common->wcmKeys, BTN_0))
			goto pad_init;

		wcmLog(priv, W_ERROR, "unable to ioctl xmax value.\n");
		return !Success;
	}

	if (absinfo.maximum <= 0)
	{
		wcmLog(priv, W_ERROR, "xmax value is %d, expected > 0.\n",
			absinfo.maximum);
		return !Success;
	}

	if (!is_touch)
	{
		common->wcmMinX = absinfo.minimum;
		common->wcmMaxX = absinfo.maximum;
		common->wcmResolX = absinfo.resolution * 1000;
	}
	else
	{
		common->wcmMaxTouchX = absinfo.maximum;
		common->wcmTouchResolX = absinfo.resolution * 1000;
	}

	/* max y */
	if (ioctl(wcmGetFd(priv), EVIOCGABS(ABS_Y), &absinfo) < 0)
	{
		wcmLog(priv, W_ERROR, "unable to ioctl ymax value.\n");
		return !Success;
	}

	if (absinfo.maximum <= 0)
	{
		wcmLog(priv, W_ERROR, "ymax value is %d, expected > 0.\n", absinfo.maximum);
		return !Success;
	}

	if (!is_touch)
	{
		common->wcmMinY = absinfo.minimum;
		common->wcmMaxY = absinfo.maximum;
		common->wcmResolY = absinfo.resolution * 1000;
	}
	else
	{
		common->wcmMaxTouchY = absinfo.maximum;
		common->wcmTouchResolY = absinfo.resolution * 1000;
	}

	/* max finger strip X for tablets with Expresskeys
	 * or physical X for touch devices in hundredths of a mm */
	if (ISBITSET(abs, ABS_RX) &&
			!ioctl(wcmGetFd(priv), EVIOCGABS(ABS_RX), &absinfo))
	{
		if (is_touch)
			common->wcmTouchResolX =
				(int)(((double)common->wcmMaxTouchX * 100000.0
				 / (double)absinfo.maximum) + 0.5);
		else
			common->wcmMaxStripX = absinfo.maximum;
	}

	/* max touchring value for standalone pad tools */
	common->wcmMinRing = 0;
	common->wcmMaxRing = 71;
	if (!ISBITSET(ev,EV_MSC) && ISBITSET(abs, ABS_WHEEL) &&
			!ioctl(wcmGetFd(priv), EVIOCGABS(ABS_WHEEL), &absinfo))
	{
		common->wcmMinRing = absinfo.minimum;
		common->wcmMaxRing = absinfo.maximum;
	}

	/* X tilt range */
	if (ISBITSET(abs, ABS_TILT_X) &&
			!ioctl(wcmGetFd(priv), EVIOCGABS(ABS_TILT_X), &absinfo))
	{
		/* If resolution is specified */
		if (absinfo.resolution > 0)
		{
			/* Assume the range is centered on zero */
			common->wcmTiltOffX = 0;
			/* Convert to resolution expected by applications */
			common->wcmTiltFactX = TILT_RES /
					       (double)absinfo.resolution;
		}
		else
		{
			/*
			 * Center the reported range on zero to support
			 * kernel drivers still reporting non-zero-centered
			 * values.
			 */
			common->wcmTiltOffX = - (absinfo.minimum +
						 absinfo.maximum) / 2;
			/*
			 * Assume reported resolution is the one expected by
			 * applications
			 */
			common->wcmTiltFactX = 1.0;
		}
		common->wcmTiltMinX = round((absinfo.minimum +
					     common->wcmTiltOffX) *
					    common->wcmTiltFactX);
		common->wcmTiltMaxX = round((absinfo.maximum +
					     common->wcmTiltOffX) *
					    common->wcmTiltFactX);
	}

	/* Y tilt range */
	if (ISBITSET(abs, ABS_TILT_Y) &&
			!ioctl(wcmGetFd(priv), EVIOCGABS(ABS_TILT_Y), &absinfo))
	{
		/* If resolution is specified */
		if (absinfo.resolution > 0)
		{
			/* Assume the range is centered on zero */
			common->wcmTiltOffY = 0;
			/* Convert to resolution expected by applications */
			common->wcmTiltFactY = TILT_RES /
					       (double)absinfo.resolution;
		}
		else
		{
			/*
			 * Center the reported range on zero to support
			 * kernel drivers still reporting non-zero-centered
			 * values.
			 */
			common->wcmTiltOffY = - (absinfo.minimum +
						 absinfo.maximum) / 2;
			/*
			 * Assume reported resolution is the one expected by
			 * applications
			 */
			common->wcmTiltFactY = 1.0;
		}
		common->wcmTiltMinY = round((absinfo.minimum +
					     common->wcmTiltOffY) *
					    common->wcmTiltFactY);
		common->wcmTiltMaxY = round((absinfo.maximum +
					     common->wcmTiltOffY) *
					    common->wcmTiltFactY);
	}

	/* max finger strip Y for tablets with Expresskeys
	 * or physical Y for touch devices in hundredths of a mm */
	if (ISBITSET(abs, ABS_RY) &&
			!ioctl(wcmGetFd(priv), EVIOCGABS(ABS_RY), &absinfo))
	{
		if (is_touch)
			common->wcmTouchResolY =
				 (int)(((double)common->wcmMaxTouchY * 100000.0
				 / (double)absinfo.maximum) + 0.5);
		else
			common->wcmMaxStripY = absinfo.maximum;
	}

	/* max z cannot be configured */
	if (ISBITSET(abs, ABS_PRESSURE) &&
			!ioctl(wcmGetFd(priv), EVIOCGABS(ABS_PRESSURE), &absinfo))
		common->wcmMaxZ = absinfo.maximum;

	/* max distance */
	if (ISBITSET(abs, ABS_DISTANCE) &&
			!ioctl(wcmGetFd(priv), EVIOCGABS(ABS_DISTANCE), &absinfo))
		common->wcmMaxDist = absinfo.maximum;

	if (ISBITSET(abs, ABS_MT_SLOT))
	{
		private->wcmUseMT = 1;

		if (!ioctl(wcmGetFd(priv), EVIOCGABS(ABS_MT_SLOT), &absinfo))
			common->wcmMaxContacts = absinfo.maximum + 1;

		/* pen and MT on the same logical port */
		if (ISBITSET(common->wcmKeys, BTN_TOOL_PEN))
			private->wcmPenTouch = TRUE;
	}

	/* Non-wacom devices, and Wacom devices without an ABS_MISC should be treated as generic */
	if (common->vendor_id != WACOM_VENDOR_ID || !ISBITSET(abs, ABS_MISC))
		common->wcmProtocolLevel = WCM_PROTOCOL_GENERIC;

	if (ioctl(wcmGetFd(priv), EVIOCGBIT(EV_SW, sizeof(sw)), sw) < 0)
	{
		wcmLog(priv, W_ERROR, "unable to ioctl sw bits.\n");
		return 0;
	}
	else if (ISBITSET(sw, SW_MUTE_DEVICE))
	{
		common->wcmHasHWTouchSwitch = TRUE;

		memset(sw, 0, sizeof(sw));

		if (ioctl(wcmGetFd(priv), EVIOCGSW(sizeof(sw)), sw) < 0)
			wcmLog(priv, W_ERROR, "unable to ioctl sw state.\n");

		if (ISBITSET(sw, SW_MUTE_DEVICE))
			common->wcmHWTouchSwitchState = 0;
		else
			common->wcmHWTouchSwitchState = 1;
	}

pad_init:
	usbWcmInitPadState(priv);

	return Success;
}

static int usbDetectConfig(WacomDevicePtr priv)
{
	WacomCommonPtr common = priv->common;
	wcmUSBData *usbdata = common->private;

	DBG(10, common, "\n");
	if (IsPad (priv))
		priv->nbuttons = usbdata->npadkeys;
	else
		priv->nbuttons = usbdata->nbuttons;

	if (!priv->wcmProxoutDist) {
		priv->wcmProxoutDist
			= common->wcmProxoutDistDefault;
		if (IsStylus(priv))
			priv->wcmProxoutDist = 30;
	}
	return TRUE;
}

static int usbParse(WacomDevicePtr priv, const unsigned char* data, unsigned long len)
{
	WacomCommonPtr common = priv->common;
	struct input_event event;

	if (len < sizeof(struct input_event))
		return 0;

	memcpy(&event, data, sizeof(event));
	usbParseEvent(priv, &event);
	return common->wcmPktLength;
}

/**
 * Returns a serial number for the provided device_type and serial, as
 * through it came from from a Protocol 5 device.
 *
 * Protocol 5 serial numbers will be returned unchanged. Otherwise,
 * anonymous tools (from Protocol 4 and Generic Protocol) will have
 * serial numbers of: -1 (pad), 1 (pen/1st finger), 2 (2nd finger),
 * etc.
 *
 * @param[in] device_type  Type of device (e.g. STYLUS_ID, TOUCH_ID, PAD_ID)
 * @param[in] serial       Serial number of device
 * @return                 Serial number of device as through from Protocol 5
 */
static unsigned int protocol5Serial(int device_type, unsigned int serial) {
	if (!serial) {
		/* Generic Protocol does not send serial numbers */
		return device_type == PAD_ID ? DEFAULT_TOOL_SERIAL : 1;
	}
	else if (serial == 0xf0) {
		/* Protocol 4 uses the expected anonymous serial
		 * numbers, but has the wrong PAD serial number.
		 * This could cause problem if 0xf0 is ever used
		 * for a Protocol 5 serial number, but isn't a
		 * problem as yet.
		 */
		return DEFAULT_TOOL_SERIAL;
	}
	else {
		/* Protocol 5 FTW */
		return serial;
	}
}

/**
 * Find an appropriate channel to track the specified tool's state in.
 * If the tool is already in proximity, the channel currently being used
 * to store its state will be returned. Otherwise, an arbitrary available
 * channel will be cleaned and returned. Up to MAX_CHANNEL tools can be
 * tracked concurrently by driver.
 *
 * @param[in] common
 * @param[in] device_type  Type of tool (e.g. STYLUS_ID, TOUCH_ID, PAD_ID)
 * @param[in] serial       Serial number of tool
 * @return                 Channel number to track the tool's state
 */
static int usbChooseChannel(WacomCommonPtr common, int device_type, unsigned int serial)
{
	/* figure out the channel to use based on serial number */
	int i, channel = -1;

	/* force events from PAD device to PAD_CHANNEL */
	if (serial == DEFAULT_TOOL_SERIAL)
		channel = PAD_CHANNEL;

	/* find existing channel */
	if (channel < 0)
	{
		for (i=0; i<MAX_CHANNELS; i++)
		{
			if (common->wcmChannel[i].work.proximity &&
			    common->wcmChannel[i].work.device_type == device_type &&
			    common->wcmChannel[i].work.serial_num == serial)
			{
				channel = i;
				break;
			}
		}
	}

	/* find and clean an empty channel */
	if (channel < 0)
	{
		for (i=0; i<MAX_CHANNELS; i++)
		{
			if (i == PAD_CHANNEL)
				continue;

			if (!common->wcmChannel[i].work.proximity &&
			    !common->wcmChannel[i].valid.state.proximity)
			{
				channel = i;
				memset(&common->wcmChannel[channel],0, sizeof(WacomChannel));
				break;
			}
		}
	}

	/* fresh out of channels */
	if (channel < 0)
	{
		/* This should never happen in normal use.
		 * Let's start over again. Force prox-out for all channels.
		 */
		for (i=0; i<MAX_CHANNELS; i++)
		{
			if (i == PAD_CHANNEL)
				continue;

			if (common->wcmChannel[i].work.proximity &&
			    (common->wcmChannel[i].work.serial_num != DEFAULT_TOOL_SERIAL))
			{
				common->wcmChannel[i].work.proximity = 0;
				/* dispatch event */
				wcmEvent(common, i, &common->wcmChannel[i].work);
				DBG(2, common, "free channels: dropping %u\n",
						common->wcmChannel[i].work.serial_num);
			}
		}
		DBG(1, common, "device with serial number: %u"
		    " at %u: Exceeded channel count; ignoring the events.\n",
		    serial, wcmTimeInMillis());
	}

	return channel;
}

static inline void
usbResetEventCounter(wcmUSBData *private)
{
	private->wcmEventCnt = 0;
	private->wcmEventFlags = 0;
}

static void usbParseEvent(WacomDevicePtr priv,
	const struct input_event* event)
{
	WacomCommonPtr common = priv->common;
	wcmUSBData* private = common->private;

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

	wcmNotifyEvdev(priv, event);

	/* store events until we receive a SYN_REPORT */

	/* space left? bail if not. */
	if (private->wcmEventCnt >= ARRAY_SIZE(private->wcmEvents))
	{
		wcmLogSafe(priv, W_ERROR, "%s: usbParse: Exceeded event queue (%u) \n",
		       priv->name, private->wcmEventCnt);
		usbResetEventCounter(private);
		return;
	}

	/* save it for later */
	private->wcmEvents[private->wcmEventCnt++] = *event;
	private->wcmEventFlags |= 1 << event->type;

	switch (event->type)
	{
		case EV_MSC:
			usbParseMscEvent(priv, event);
			break;
		case EV_SYN:
			usbParseSynEvent(priv, event);
			break;
		default:
			break;
	}
}

static void usbParseMscEvent(WacomDevicePtr priv,
			     const struct input_event *event)
{
	WacomCommonPtr common = priv->common;
	wcmUSBData* private = common->private;

	if (event->code != MSC_SERIAL)
		return;

	if (event->value != 0)
	{
		/* save the serial number so we can look up the channel number later */
		private->wcmLastToolSerial = event->value;
	}
	else
	{
		/* If this is the first time to set wcmLastToolSerial and serial
		 * number is 0, it means the value is not ready. Just wait for a
		 * non-zero one. However, if we are in the middle of an established
		 * established reporting process, serial number should never be 0.
		 * Report the error and show the serial number that we expect.
		 *
		 * In both cases, we drop the whole frame.
		 */
		if (private->wcmLastToolSerial)
			wcmLogSafe(priv, W_ERROR,
				      "%s: usbParse: Ignoring packet for serial=0. It should be %ud \n",
				      priv->name, private->wcmLastToolSerial);
		usbResetEventCounter(private);
	}
}

/**
 * EV_SYN marks the end of a set of events containing axes and button info.
 * Check for valid data and hand over to dispatch to extract the actual
 * values and process them. At this point, all events up to the EV_SYN are
 * queued up in wcmEvents.
 */
static void usbParseSynEvent(WacomDevicePtr priv,
			     const struct input_event *event)
{
	WacomCommonPtr common = priv->common;
	wcmUSBData* private = common->private;
	const uint32_t significant_event_types = ~(1 << EV_SYN | 1 << EV_MSC);

	if (event->code != SYN_REPORT)
		return;

	/* ignore events without information */
	if ((private->wcmEventCnt < 2) && private->wcmLastToolSerial)
	{
		DBG(3, common, "%s: dropping empty event for serial %u\n",
		    priv->name, private->wcmLastToolSerial);
		goto skipEvent;
	}


	/* If all we get in an event frame is EV_SYN/EV_MSC, we don't have
	 * real data to process. */
	if ((private->wcmEventFlags & significant_event_types) == 0)
	{
		DBG(6, common, "no real events received\n");
		goto skipEvent;
	}

	/* dispatch all queued events */
	usbDispatchEvents(priv);

skipEvent:
	usbResetEventCounter(private);
}

static int usbFilterEvent(WacomCommonPtr common, struct input_event *event)
{
	wcmUSBData* private = common->private;

	/* For devices that report multitouch, the following list is a set of
	 * duplicate data from one slot and needs to be filtered out.
	 */
	if (private->wcmUseMT)
	{
		if (event->type == EV_KEY)
		{
			switch(event->code)
			{
				case BTN_TOUCH:
				case BTN_TOOL_FINGER:
				case BTN_TOOL_DOUBLETAP:
				case BTN_TOOL_TRIPLETAP:
					return 1;
			}
		}
		else if (event->type == EV_ABS)
		{
			if (private->wcmDeviceType == TOUCH_ID)
			{
				/* filter ST for MT */
				switch(event->code)
				{
					case ABS_X:
					case ABS_Y:
					case ABS_PRESSURE:
						return 1;
				}
			}
			else
			{
				/* filter MT for pen */
				switch(event->code)
				{
					case ABS_MT_SLOT:
					case ABS_MT_TRACKING_ID:
					case ABS_MT_POSITION_X:
					case ABS_MT_POSITION_Y:
					case ABS_MT_PRESSURE:
						return 1;
				}
			}
		}
	}

	/* For generic devices, filter out doubletap/tripletap that
	 * can be confused with older protocol.
	 */
	if (common->wcmProtocolLevel == WCM_PROTOCOL_GENERIC)
	{
		if (event->type == EV_KEY)
		{
			switch(event->code)
			{
				case BTN_TOOL_DOUBLETAP:
				case BTN_TOOL_TRIPLETAP:
					return 1;
			}
		}
	}

	return 0;
}

/**
 * Determine if this is an AES tool or not from the tool ID. Bit 0
 * of the device ID is currently defined to be such a flag, but
 * older tablet pens used this bit for other purposes.
 *
 * @param common A reference to the device's common structure
 * @param id The tool id received from the kernel.
 * @return 'true' if the tool uses AES, 'false' otherwise.
 */
static Bool toolIdIsAes(WacomCommonPtr common, int id)
{
	if (TabletHasFeature(common, WCM_LEGACY_IDS))
		return FALSE;

	return id & 0x01;
}

#define ERASER_BIT      0x008
#define PUCK_BITS	0xf00
#define PUCK_EXCEPTION  0x806
/**
 * Decide the tool type by its id for protocol 5 devices
 *
 * @param common A reference to the device's common structure
 * @param id The tool id received from the kernel.
 * @return The tool type associated with the tool id.
 */
static int usbIdToType(WacomCommonPtr common, int id)
{
	int type = STYLUS_ID;

	if (!id)
		return 0;

	if (toolIdIsAes(common, id)) {
		/* Type information is not available from the ID for AES */
		return 0;
	}
	else {
		/* The existing EMR tool ids have the following patten: all
		 * pucks, except one, have the third byte set to zero; all
		 * erasers have the fourth bit set. The rest are styli.
		 */
		if (id & ERASER_BIT)
			type = ERASER_ID;
		else if (!(id & PUCK_BITS) || (id == PUCK_EXCEPTION))
			type = CURSOR_ID;
	}

	return type;
}

/**
 * Find the tool type (STYLUS_ID, etc.) based on the device_id.
 *
 * Protocol 5 devices report different IDs for different styli and pucks,
 * Protocol 4 devices simply report STYLUS_DEVICE_ID, etc.
 *
 * @param common A reference to the device's common structure
 * @param device_id id of the device
 * @return device type
 */
static int usbFindDeviceTypeById(WacomCommonPtr common, int device_id)
{
	switch (device_id)
	{
		case STYLUS_DEVICE_ID:
			return STYLUS_ID;
		case ERASER_DEVICE_ID:
			return ERASER_ID;
		case CURSOR_DEVICE_ID:
			return CURSOR_ID;
		case TOUCH_DEVICE_ID:
			return TOUCH_ID;
		case PAD_DEVICE_ID:
			return PAD_ID;
		default: /* protocol 5 */
			return usbIdToType(common, device_id);
	}
}

/**
 * Handle ABS events strictly according to their definition as documented
 * in the Linux kernel.
 *
 * @param common
 * @param event
 * @param channel_number
 */
static int usbParseGenericAbsEvent(WacomCommonPtr common,
			    struct input_event *event, int channel_number)
{
	WacomChannel *channel = &common->wcmChannel[channel_number];
	WacomDeviceState *ds = &channel->work;
	int change = 1;

	switch(event->code)
	{
		case ABS_X:
			ds->x = event->value;
			break;
		case ABS_Y:
			ds->y = event->value;
			break;
		case ABS_RZ:
			ds->rotation = event->value;
			break;
		case ABS_TILT_X:
			ds->tiltx = round((event->value + common->wcmTiltOffX) *
					  common->wcmTiltFactX);
			break;
		case ABS_TILT_Y:
			ds->tilty = round((event->value + common->wcmTiltOffY) *
					  common->wcmTiltFactY);
			break;
		case ABS_PRESSURE:
			ds->pressure = event->value;
			break;
		case ABS_DISTANCE:
			ds->distance = event->value;
			break;
		case ABS_WHEEL:
			ds->abswheel = event->value;
			break;
		case ABS_THROTTLE:
			ds->throttle = event->value;
			break;
		default:
			change = 0;
			break;
	}

	return change;
}

/**
 * Handle ABS events according to Wacom (protocol 4/5) conventions. Only
 * those events which are not interpreted in the generic manner need to be
 * handled here.
 *
 * @param common
 * @param event
 * @param channel_number
 */
static int usbParseWacomAbsEvent(WacomCommonPtr common,
			    struct input_event *event, int channel_number)
{
	WacomChannel *channel = &common->wcmChannel[channel_number];
	WacomDeviceState *ds = &channel->work;
	int change = 1;

	switch(event->code) {
		case ABS_RX:
			ds->stripx = event->value;
			break;
		case ABS_RY:
			ds->stripy = event->value;
			break;
		case ABS_Z:
			ds->abswheel = event->value;
			break;
		case ABS_THROTTLE:
			/* 2nd touch ring comes in over ABS_THROTTLE for 24HD */
			if ((common->vendor_id == WACOM_VENDOR_ID) &&
			    (common->tablet_id == 0xF4 || common->tablet_id == 0xF8))
				ds->abswheel2 = event->value;
			break;
		case ABS_MISC:
			ds->proximity = (event->value != 0);
			if (event->value)
				ds->device_id = event->value;
			break;
		default:
			change = 0;
			break;
	}

	return change;
}

/**
 * Handle an incoming ABS event.
 *
 * @param common
 * @param event
 * @param channel_number
 */
static void usbParseAbsEvent(WacomCommonPtr common,
			    struct input_event *event, int channel_number)
{
	WacomChannel *channel = &common->wcmChannel[channel_number];
	WacomDeviceState *ds = &channel->work;
	Bool change;

	change = usbParseGenericAbsEvent(common, event, channel_number);
	if (common->wcmProtocolLevel != WCM_PROTOCOL_GENERIC) {
		change |= usbParseWacomAbsEvent(common, event, channel_number);
	}

	ds->time = wcmTimeInMillis();
	channel->dirty |= change;
}

/**
 * Flip the mask bit in buttons corresponding to btn to the specified state.
 *
 * @param buttons The current button mask
 * @param btn Zero-indexed button number to change
 * @param state Zero to unset, non-zero to set the mask for the button
 *
 * @return The new button mask
 */
static unsigned int
mod_buttons(WacomCommonPtr common, unsigned int buttons, unsigned int btn, Bool state)
{
	unsigned int mask;

	if (btn >= sizeof(int) * 8)
	{
		wcmLogCommonSafe(common, W_ERROR,
		       "%s: Invalid button number %u. Insufficient storage\n",
		       __func__, btn);
		return buttons;
	}

	mask = 1u << btn;

	if (state)
		buttons |= mask;
	else
		buttons &= ~mask;

	return buttons;
}

static void usbParseAbsMTEvent(WacomCommonPtr common, struct input_event *event)
{
	int change = 1;
	wcmUSBData* private = common->private;
	WacomDeviceState *ds;

	ds = &common->wcmChannel[private->wcmMTChannel].work;

	switch(event->code)
	{
		case ABS_MT_SLOT:
			if (event->value >= 0) {
				unsigned int serial = event->value + 1;
				private->wcmMTChannel = usbChooseChannel(common, TOUCH_ID, serial);
				if (private->wcmMTChannel < 0)
					return;
				ds = &common->wcmChannel[private->wcmMTChannel].work;
				ds->serial_num = serial;
			}
			break;

		case ABS_MT_TRACKING_ID:
			ds->proximity = (event->value != -1);
			/* set this here as type for this channel doesn't get set in usbDispatchEvent() */
			ds->device_type = TOUCH_ID;
			ds->device_id = TOUCH_DEVICE_ID;
			ds->sample = wcmTimeInMillis();
			break;

		case ABS_MT_POSITION_X:
			ds->x = event->value;
			break;

		case ABS_MT_POSITION_Y:
			ds->y = event->value;
			break;

		case ABS_MT_PRESSURE:
			ds->pressure = event->value;
			break;

		default:
			change = 0;
			break;
	}

	ds->time = wcmTimeInMillis();
	(&common->wcmChannel[private->wcmMTChannel])->dirty |= change;
}

static void usbParseKeyEvent(WacomCommonPtr common,
			    struct input_event *event, int channel_number)
{
	int change = 1;
	WacomChannel *channel = &common->wcmChannel[channel_number];
	WacomDeviceState *ds = &channel->work;
	WacomDeviceState *dslast = &channel->valid.state;

	/* BTN_TOOL_* are sent to indicate when a specific tool is going
	 * in our out of proximity.  When going in proximity, here we
	 * initialize tool specific values.  Making sure shared values
	 * are correct values during tool change is done elsewhere.
	 */
	switch (event->code)
	{
		case BTN_TOOL_PEN:
		case BTN_TOOL_PENCIL:
		case BTN_TOOL_BRUSH:
		case BTN_TOOL_AIRBRUSH:
			/* V5 tools use ABS_MISC to report device_id */
			if (common->wcmProtocolLevel != WCM_PROTOCOL_5)
				ds->device_id = STYLUS_DEVICE_ID;
			ds->proximity = (event->value != 0);
			DBG(6, common,
			    "USB stylus detected %x\n",
			    event->code);
			break;

		case BTN_TOOL_RUBBER:
			/* V5 tools use ABS_MISC to report device_id */
			if (common->wcmProtocolLevel != WCM_PROTOCOL_5)
				ds->device_id = ERASER_DEVICE_ID;
			ds->proximity = (event->value != 0);
			DBG(6, common,
			    "USB eraser detected %x (value=%d)\n",
			    event->code, event->value);
			break;

		case BTN_TOOL_MOUSE:
		case BTN_TOOL_LENS:
			DBG(6, common,
			    "USB mouse detected %x (value=%d)\n",
			    event->code, event->value);
			/* V5 tools use ABS_MISC to report device_id */
			if (common->wcmProtocolLevel != WCM_PROTOCOL_5)
				ds->device_id = CURSOR_DEVICE_ID;
			ds->proximity = (event->value != 0);
			break;

               case BTN_TOUCH:
			if (common->wcmProtocolLevel == WCM_PROTOCOL_GENERIC)
			{
				/* 1FG USB touchscreen */
				if (!TabletHasFeature(common, WCM_PEN) &&
					TabletHasFeature(common, WCM_1FGT) &&
					TabletHasFeature(common, WCM_LCD))
				{
					DBG(6, common,
					    "USB 1FG Touch detected %x (value=%d)\n",
					    event->code, event->value);
					ds->device_id = TOUCH_DEVICE_ID;
					ds->proximity = event->value;
				}
			}
			break;

		case BTN_TOOL_FINGER:
			/* A pad tool */
			if (common->wcmProtocolLevel != WCM_PROTOCOL_GENERIC)
			{
				DBG(6, common,
				    "USB Pad detected %x (value=%d)\n",
				event->code, event->value);
				ds->device_id = PAD_DEVICE_ID;
				ds->proximity = (event->value != 0);
				break;
			}
			_fallthrough_;
		case BTN_TOOL_DOUBLETAP:
			DBG(6, common,
			    "USB Touch detected %x (value=%d)\n",
			    event->code, event->value);
			ds->device_id = TOUCH_DEVICE_ID;
			ds->proximity = event->value;
			/* time stamp for 2FGT gesture events */
			if ((ds->proximity && !dslast->proximity) ||
			    (!ds->proximity && dslast->proximity))
				ds->sample = wcmTimeInMillis();
			break;

		case BTN_TOOL_TRIPLETAP:
			DBG(6, common,
			    "USB Touch second finger detected %x (value=%d)\n",
			    event->code, event->value);
			ds->device_id = TOUCH_DEVICE_ID;
			ds->proximity = event->value;
			/* time stamp for 2GT gesture events */
			if ((ds->proximity && !dslast->proximity) ||
			    (!ds->proximity && dslast->proximity))
				ds->sample = wcmTimeInMillis();
			/* Second finger events will be considered in
			 * combination with the first finger data */
			break;

		default:
			change = 0;
			break;
	}

	ds->time = wcmTimeInMillis();
	channel->dirty |= change;

	if (change)
		return;

	/* Rest back to non-default value for next switch statement */
	change = 1;

	/* From this point on, all BTN_* will be real button presses.
	 * Stylus buttons always go with *ds.  Handle remaining
	 * cases upon return.
	 */
	switch (event->code)
	{
		case BTN_STYLUS:
			ds->buttons = mod_buttons(common, ds->buttons, 1, event->value);
			break;

		case BTN_STYLUS2:
			ds->buttons = mod_buttons(common, ds->buttons, 2, event->value);
			break;

		case BTN_STYLUS3:
			ds->buttons = mod_buttons(common, ds->buttons, 3, event->value);
			break;

		default:
			change = 0;
			break;
	}

	ds->time = wcmTimeInMillis();
	channel->dirty |= change;
}

/* Handle all button presses except for stylus buttons */
static void usbParseBTNEvent(WacomCommonPtr common,
			    struct input_event *event, int channel_number)
{
	int nkeys;
	int change = 1;
	wcmUSBData *usbdata = common->private;
	WacomChannel *channel = &common->wcmChannel[channel_number];
	WacomDeviceState *ds = &channel->work;

	switch (event->code)
	{
		case BTN_LEFT:
			ds->buttons = mod_buttons(common, ds->buttons, 0, event->value);
			break;

		case BTN_MIDDLE:
			ds->buttons = mod_buttons(common, ds->buttons, 1, event->value);
			break;

		case BTN_RIGHT:
			ds->buttons = mod_buttons(common, ds->buttons, 2, event->value);
			break;

		case BTN_SIDE:
		case BTN_BACK:
			ds->buttons = mod_buttons(common, ds->buttons, 3, event->value);
			break;

		case BTN_EXTRA:
		case BTN_FORWARD:
			ds->buttons = mod_buttons(common, ds->buttons, 4, event->value);
			break;
		case KEY_CONTROLPANEL:
			ds->keys = mod_buttons(common, ds->keys, IDX_KEY_CONTROLPANEL, event->value);
			break;
		case KEY_ONSCREEN_KEYBOARD:
			ds->keys = mod_buttons(common, ds->keys, IDX_KEY_ONSCREEN_KEYBOARD, event->value);
			break;
		case KEY_BUTTONCONFIG:
			ds->keys = mod_buttons(common, ds->keys, IDX_KEY_BUTTONCONFIG, event->value);
			break;
		case KEY_INFO:
			ds->keys = mod_buttons(common, ds->keys, IDX_KEY_INFO, event->value);
			break;
		default:
			for (nkeys = 0; nkeys < usbdata->npadkeys; nkeys++)
			{
				if (event->code == usbdata->padkey_code[nkeys])
				{
					ds->buttons = mod_buttons(common, ds->buttons, nkeys, event->value);
					break;
				}
			}
			if (nkeys >= usbdata->npadkeys)
				change = 0;
			break;
	}

	ds->time = wcmTimeInMillis();
	channel->dirty |= change;
}

/**
 * Translates an event code from the kernel (e.g. type: EV_ABS code: ABS_MISC value: STYLUS_DEVICE_ID)
 * into the corresponding device type for the driver (e.g. STYLUS_ID).
 *
 * @param[in] priv
 * @param[in] type      Linux input tool type (e.g. EV_KEY)
 * @param[in] code      Linux input tool code (e.g. BTN_STYLUS_PEN)
 * @param[in] value     Linux input tool value (e.g. STYLUS_DEVICE_ID)
 * @return              Wacom device ID (e.g. STYLUS_ID) or 0 if no match.
 */
static int deviceTypeFromEvent(WacomDevicePtr priv, int type, int code, int value)
{
	WacomCommonPtr common = priv->common;
	wcmUSBData* private = common->private;

	if (type == EV_KEY) {
		switch(code) {
			case BTN_TOOL_PEN:
			case BTN_TOOL_PENCIL:
			case BTN_TOOL_BRUSH:
			case BTN_TOOL_AIRBRUSH:
				return STYLUS_ID;

			case BTN_TOOL_MOUSE:
			case BTN_TOOL_LENS:
				return CURSOR_ID;

			case BTN_TOOL_FINGER:
				if ((common->wcmProtocolLevel != WCM_PROTOCOL_GENERIC)
				    && !private->wcmUseMT)  /* this isn't in usbParseKeyEvent() */
					return PAD_ID;
				else
					return TOUCH_ID;

			case BTN_TOOL_RUBBER:
				return ERASER_ID;

			case BTN_TOOL_DOUBLETAP:
			case BTN_TOOL_TRIPLETAP:
				return TOUCH_ID;

			case BTN_TOUCH:
				if (common->wcmProtocolLevel == WCM_PROTOCOL_GENERIC &&
				    /* 1FG USB touchscreen */
				    !TabletHasFeature(common, WCM_PEN) &&
				    TabletHasFeature(common, WCM_1FGT) &&
				    TabletHasFeature(common, WCM_LCD))
					return TOUCH_ID;
				else
					break;
		}
	}
	else if (type == EV_ABS) {
		switch (code) {
			case ABS_MT_SLOT:
			case ABS_MT_TRACKING_ID:
				return TOUCH_ID;
			case ABS_MISC:
				if (common->wcmProtocolLevel != WCM_PROTOCOL_GENERIC)
					return usbFindDeviceTypeById(common, value);
		}
	}

	return 0;
}

/**
 * Queries the kernel through EVIOCGKEY for the latest device type
 * information. The result is the first tool type (e.g. STYLUS_ID)
 * found associated with the in-prox tool.
 *
 * @param[in] priv
 * @return            A tool type (e.g. STYLUS_ID) associated with the in-prox tool
 */
static int refreshDeviceType(WacomDevicePtr priv, int fd)
{
	WacomCommonPtr common = priv->common;
	int device_type = 0;
	unsigned long keys[NBITS(KEY_MAX)] = { 0 };
	int rc = ioctl(fd, EVIOCGKEY(sizeof(keys)), keys);
	int i;

	if (rc == -1) {
		wcmLog(priv, W_ERROR, "failed to retrieve key bits on %s\n", common->device_path);
		return 0;
	}

	for (i = 0; i < KEY_MAX; i++)
	{
		if (ISBITSET(keys, i))
			device_type = deviceTypeFromEvent(priv, EV_KEY, i, 0);
		if (device_type)
			return device_type;
	}

	return 0;
}

static Bool eventCouldBeFromPad(WacomDevicePtr priv,
			       const struct input_event *event_ptr)
{
	WacomCommonPtr common = priv->common;
	wcmUSBData *usbdata = common->private;
	int nkeys;

	if (event_ptr->type == EV_KEY) {

		switch (event_ptr->code) {
		case BTN_LEFT:
		case BTN_MIDDLE:
		case BTN_RIGHT:
		case BTN_SIDE:
		case BTN_BACK:
		case BTN_EXTRA:
		case BTN_FORWARD:
			return TRUE;
		default:
			for (nkeys = 0; nkeys < usbdata->npadkeys; nkeys++)
			{
				if (event_ptr->code == usbdata->padkey_code[nkeys]) {
					return TRUE;
				}
			}
			break;
		}
	}
	if (event_ptr->type == EV_REL) {
		switch (event_ptr->code) {
		case REL_WHEEL:
		case REL_HWHEEL:
		case REL_WHEEL_HI_RES:
		case REL_HWHEEL_HI_RES:
			return TRUE;
		}
	}
	if (event_ptr->type == EV_ABS) {
		switch (event_ptr->code) {
		case ABS_WHEEL:
		case ABS_THROTTLE:
			return TRUE;
		}
	}

	return FALSE;
}

/***
 * Retrieve the tool type from an USB data packet by looking at the event
 * codes. Refer to linux/input.h for event codes that define tool types.
 *
 * @param[in] priv
 * @param[in] event_ptr A pointer to the USB data packet that contains the
 * events to be processed.
 * @param[in] nevents Number of events in the packet.
 * @param[in] last_device_type The device type for the last event
 *
 * @return The tool type. This falls back on last_device_type if no
 *         pen/touch/eraser event code in the event, and on EVIOCGKEY
 *         if last_device_type is not a tool. If all else fails, '0'
 *         is returned.
 */
static int usbInitToolType(WacomDevicePtr priv, int fd,
                           const struct input_event *event_ptr,
                           int nevents, int last_device_type)
{
	int i, device_type = 0;

	for (i = 0; (i < nevents) && !device_type; ++i)
	{
		device_type = deviceTypeFromEvent(priv, event_ptr[i].type, event_ptr[i].code, event_ptr[i].value);
	}

	if (!device_type)
		device_type = last_device_type;

	if (!device_type)
		device_type = refreshDeviceType(priv, fd);

	if (!device_type) /* expresskey pressed at startup or missing type */
		for (i = 0; (i < nevents) && !device_type; ++i)
			if (eventCouldBeFromPad(priv, &event_ptr[i]))
				device_type = PAD_ID;

	return device_type;
}

/**
 * Check if the tool is a stylus/eraser/cursor and in-prox or not.
 *
 * @param device_type The tool type stored in wcmChannel
 * @param proximity The tool's proximity state

 * @return True if stylus/eraser/cursor is in-prox; False otherwise.
 */
static Bool usbIsTabletToolInProx(int device_type, int proximity)
{
	Bool is_tablet_tool = (device_type == STYLUS_ID) ||
				(device_type == CURSOR_ID) ||
				(device_type == ERASER_ID);
	return (is_tablet_tool && proximity);
}

static void usbDispatchEvents(WacomDevicePtr priv)
{
	int c;
	WacomDeviceState *ds;
	struct input_event* event;
	WacomCommonPtr common = priv->common;
	int channel;
	wcmUSBData* private = common->private;
	WacomDeviceState dslast = common->wcmChannel[private->lastChannel].valid.state;

	DBG(6, common, "%u events received\n", private->wcmEventCnt);

	private->wcmDeviceType = usbInitToolType(priv, wcmGetFd(priv),
	                                         private->wcmEvents,
	                                         private->wcmEventCnt,
	                                         dslast.device_type);

	if (private->wcmPenTouch)
	{
		/* We get both tablet tool and touch data from the kernel when
		 * both tools are in/down. So, if we were (hence the need of dslast)
		 * processing tablet tool events, we should ignore touch events.
		 *
		 * MT events will be posted to the userland when XInput 2.1
		 * is ready.
		 */
		if ((private->wcmDeviceType == TOUCH_ID) &&
				usbIsTabletToolInProx(dslast.device_type, dslast.proximity))
		{
			private->wcmEventCnt = 0;
			return;
		}
	}

	private->wcmLastToolSerial = protocol5Serial(private->wcmDeviceType, private->wcmLastToolSerial);
	channel = usbChooseChannel(common, private->wcmDeviceType, private->wcmLastToolSerial);

	/* couldn't decide channel? invalid data */
	if (channel == -1) {
		private->wcmEventCnt = 0;
		return;
	}

	ds = &common->wcmChannel[channel].work;
	dslast = common->wcmChannel[channel].valid.state;

	if (ds->device_type && ds->device_type != private->wcmDeviceType)
		wcmLogSafe(priv, W_ERROR,
				      "usbDispatchEvents: Device Type mismatch - %d -> %d. This is a BUG.\n",
				      ds->device_type, private->wcmDeviceType);
	/* no device type? */
	if (!ds->device_type && private->wcmDeviceType) {
		ds->device_type = private->wcmDeviceType;
		ds->proximity = 1;
	}

	/* all USB data operates from previous context except relative values*/
	ds->relwheel = 0;
	ds->relwheel2 = 0;
	ds->serial_num = private->wcmLastToolSerial;

	/* loop through all events in group */
	for (unsigned int i = 0; i < private->wcmEventCnt; ++i)
	{
		event = private->wcmEvents + i;
		DBG(11, common,
			"event[%u]->type=%d code=%d value=%d\n",
			i, event->type, event->code, event->value);

		/* Check for events to be ignored and skip them up front. */
		if (usbFilterEvent(common, event))
			continue;

		if (common->wcmHasHWTouchSwitch)
		{
			if ((event->type == EV_SW) &&
			    (event->code == SW_MUTE_DEVICE))
			{
				/* touch is disabled when SW_MUTE_DEVICE is set */
				int touch_enabled = (event->value == 0);

				if (touch_enabled != common->wcmHWTouchSwitchState) {
					common->wcmHWTouchSwitchState = touch_enabled;
					/* this property is only set for touch device */
					wcmUpdateHWTouchProperty(common->wcmTouchDevice);
				}
			}
		}

		/* absolute events */
		if (event->type == EV_ABS)
		{
			usbParseAbsEvent(common, event, channel);
			usbParseAbsMTEvent(common, event);
		}
		else if (event->type == EV_REL)
		{
			switch (event->code) {
			case REL_WHEEL:
				ds->relwheel = event->value;
				ds->time = wcmTimeInMillis();
				common->wcmChannel[channel].dirty |= TRUE;
				break;
			case REL_WHEEL_HI_RES:
				/* unsupported */
				break;
			case REL_HWHEEL:
				ds->relwheel2 = event->value;
				ds->time = wcmTimeInMillis();
				common->wcmChannel[channel].dirty |= TRUE;
				break;
			case REL_HWHEEL_HI_RES:
				/* unsupported */
				break;
			default:
				wcmLogSafe(priv, W_ERROR,
						      "%s: rel event recv'd (%d)!\n",
						      priv->name,
						      event->code);
				break;
			}
		}
		else if (event->type == EV_KEY)
		{
			/* Button events can be from puck or expresskeys */
			int btn_channel = (ds->device_type == CURSOR_ID) ?
					   channel : PAD_CHANNEL;

			usbParseKeyEvent(common, event, channel);
			usbParseBTNEvent(common, event, btn_channel);
		}
	} /* next event */

	/* DTF720 and DTF720a don't support eraser */
	if (((common->tablet_id == 0xC0) || (common->tablet_id == 0xC2)) &&
		(ds->device_type == ERASER_ID))
	{
		DBG(10, common,
			"DTF 720 doesn't support eraser ");
		return;
	}

	/* verify we have minimal data when entering prox */
	if (ds->proximity && !dslast.proximity && ds->device_type != PAD_ID) {
		struct input_absinfo absinfo;

		if (!ds->x) {
			if (ioctl(wcmGetFd(priv), EVIOCGABS(ABS_X), &absinfo) < 0)
			{
				DBG(-1, common, "unable to ioctl current x value.\n");
				return;
			}
			ds->x = absinfo.value;
		}
		if (!ds->y) {
			if (ioctl(wcmGetFd(priv), EVIOCGABS(ABS_Y), &absinfo) < 0)
			{
				DBG(-1, common, "unable to ioctl current x value.\n");
				return;
			}
			ds->y = absinfo.value;
		}
	}

	/*reset the serial number when the tool is going out */
	if (!ds->proximity)
		private->wcmLastToolSerial = 0;

	private->lastChannel = channel;

	for (c = 0; c < MAX_CHANNELS; c++) {
		ds = &common->wcmChannel[c].work;

		/* walk through all channels */
		if (common->wcmChannel[c].dirty) {
			DBG(10, common, "Dirty flag set on channel %d; sending event.\n", c);
			common->wcmChannel[c].dirty = FALSE;
			/* don't send touch event when touch isn't enabled */
			if (ds->device_type != TOUCH_ID || common->wcmTouch)
				wcmEvent(common, c, ds);
		}
	}
}

/* Quirks to unify the tool and tablet types for GENERIC protocol tablet PCs
 *
 * @param[in,out] keys Contains keys queried from hardware. If a
 *   touchscreen is detected, keys are modified to add BTN_TOOL_FINGER so
 *   that a TOUCH device is created later.
 * @param[in] abs Used to detect multi-touch touchscreens.  When detected,
 *   updates keys to add possibly missing BTN_TOOL_DOUBLETAP.
 * @param[in,out] common Used only for tablet features.  Adds TCM_TPC for
 *   touchscreens so correct defaults, such as absolute mode, are used.
 */
static void usbGenericTouchscreenQuirks(unsigned long *keys,
					unsigned long *abs,
					WacomCommonPtr common)
{
	/* USB Tablet PC single finger touch devices do not emit
	 * BTN_TOOL_FINGER since it is a touchscreen device.
	 */
	if (ISBITSET(keys, BTN_TOUCH) &&
			!ISBITSET(keys, BTN_TOOL_FINGER) &&
			!ISBITSET(keys, BTN_TOOL_PEN))
	{
		SETBIT(keys, BTN_TOOL_FINGER); /* 1FGT */
		TabletSetFeature(common, WCM_TPC);
	}

	/* Serial Tablet PC two finger touch devices do not emit
	 * BTN_TOOL_DOUBLETAP since they are not touchpads.
	 */
	if (ISBITSET(abs, ABS_MT_SLOT) && !ISBITSET(keys, BTN_TOOL_DOUBLETAP))
		SETBIT(keys, BTN_TOOL_DOUBLETAP); /* 2FGT */
}

/**
 * Query the device's fd for the key bits and the tablet ID. Returns the ID
 * on success or 0 on failure.
 * For USB devices, we simply copy the information the kernel gives us.
 */
static int usbProbeKeys(WacomDevicePtr priv)
{
	struct input_id wacom_id;
	WacomCommonPtr  common = priv->common;
	unsigned long abs[NBITS(ABS_MAX)] = {0};

	if (ioctl(wcmGetFd(priv), EVIOCGBIT(EV_KEY, (sizeof(unsigned long)
						* NBITS(KEY_MAX))), common->wcmKeys) < 0)
	{
		wcmLog(priv, W_ERROR,
			    "usbProbeKeys unable to ioctl USB key bits.\n");
		return 0;
	}

	if (ioctl(wcmGetFd(priv), EVIOCGPROP(sizeof(common->wcmInputProps)), common->wcmInputProps) < 0)
	{
		wcmLog(priv, W_ERROR,
			    "usbProbeKeys unable to ioctl input properties.\n");
		return 0;
	}

	if (ioctl(wcmGetFd(priv), EVIOCGID, &wacom_id) < 0)
	{
		wcmLog(priv, W_ERROR,
			"usbProbeKeys unable to ioctl Device ID.\n");
		return 0;
	}

        if (ioctl(wcmGetFd(priv), EVIOCGBIT(EV_ABS, sizeof(abs)), abs) < 0)
	{
		wcmLog(priv, W_ERROR,
			    "usbProbeKeys unable to ioctl abs bits.\n");
		return 0;
	}

	/* The wcmKeys stored above have different meaning for generic
	 * protocol.  Detect that and change default protocol 4 to
	 * generic.
	 */
	if (!ISBITSET(abs, ABS_MISC))
	{
		common->wcmProtocolLevel = WCM_PROTOCOL_GENERIC;
		usbGenericTouchscreenQuirks(common->wcmKeys, abs, common);
	}

	common->vendor_id = wacom_id.vendor;
	common->tablet_id = wacom_id.product;

	return wacom_id.product;
}


#ifdef ENABLE_TESTS

TEST_CASE(test_mod_buttons)
{
	WacomCommonRec common = {0};
	for (size_t i = 0; i < sizeof(int) * 8; i++)
	{
		unsigned int buttons = mod_buttons(&common, 0, i, 1);
		assert(buttons == (1u << i));
		buttons = mod_buttons(&common, 0, i, 0);
		assert(buttons == 0);
	}

	assert(mod_buttons(&common, 0, sizeof(int) * 8, 1) == 0);
}


#endif

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