/*****************************************************************************
** wacusb.c
**
** Copyright (C) 2002 - 2004 - John E. Joganic
**
** 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 Lesser 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 "wacusb.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <assert.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/*****************************************************************************
** Begin USB Linux Input Subsystem
*****************************************************************************/

#ifdef WCM_ENABLE_LINUXINPUT
#include <linux/input.h>

#ifndef EV_MSC
#define EV_MSC 0x04
#endif

#ifndef MSC_SERIAL
#define MSC_SERIAL 0x00
#endif

/*****************************************************************************
** Defines
*****************************************************************************/

/* from linux/input.h */
#define BITS_PER_LONG (sizeof(long) * 8)
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
#define BIT(x)  (1UL<<((x)%BITS_PER_LONG))
#define LONG(x) ((x)/BITS_PER_LONG)
#define ISBITSET(x,y) ((x)[LONG(y)] & BIT(y))

/*****************************************************************************
** Structures
*****************************************************************************/

typedef struct _USBSUBTYPE USBSUBTYPE;
typedef struct _USBDEVICE USBDEVICE;
typedef struct _USBVENDOR USBVENDOR;

struct _USBSUBTYPE
{
	const char* pszName;
	const char* pszDesc;
	unsigned int uSubType;
	unsigned int uProduct;
};

struct _USBDEVICE
{
	const char* pszName;
	const char* pszDesc;
	unsigned int uDevice;
	USBSUBTYPE* pSubTypes;
};

struct _USBVENDOR
{
	const char* pszName;
	const char* pszDesc;
	unsigned int uVendor;
	USBDEVICE* pDevices;
};

/*****************************************************************************
** USB Tablet object
*****************************************************************************/

typedef struct _USBTABLET USBTABLET;

struct _USBTABLET
{
	WACOMTABLET_PRIV tablet;
	WACOMENGINE hEngine;
	int fd;
	USBVENDOR* pVendor;
	USBDEVICE* pDevice;
	USBSUBTYPE* pSubType;
	int nBus;
	int nVersion;
	WACOMSTATE state;
	int nToolProx;
};

/*****************************************************************************
** Static Prototypes
*****************************************************************************/

static void USBClose(WACOMTABLET_PRIV* pTablet);
static WACOMMODEL USBGetModel(WACOMTABLET_PRIV* pTablet);
static const char* USBGetVendorName(WACOMTABLET_PRIV* pTablet);
static const char* USBGetClassName(WACOMTABLET_PRIV* pTablet);
static const char* USBGetDeviceName(WACOMTABLET_PRIV* pTablet);
static const char* USBGetSubTypeName(WACOMTABLET_PRIV* pTablet);
static const char* USBGetModelName(WACOMTABLET_PRIV* pTablet);
static int USBGetROMVer(WACOMTABLET_PRIV* pTablet, int* pnMajor,
		int* pnMinor, int* pnRelease);
static int USBGetCaps(WACOMTABLET_PRIV* pTablet);
static int USBGetState(WACOMTABLET_PRIV* pTablet, WACOMSTATE* pState);
static int USBGetFD(WACOMTABLET_PRIV* pTablet);
static int USBReadRaw(WACOMTABLET_PRIV* pTablet, unsigned char* puchData,
		unsigned int uSize);
static int USBParseData(WACOMTABLET_PRIV* pTablet,
		const unsigned char* puchData, unsigned int uLength,
		WACOMSTATE* pState);

static int USBFindModel(USBTABLET* pUSB, unsigned int uVendor,
		unsigned int uProduct);
static int USBIdentifyModel(USBTABLET* pUSB);

/*****************************************************************************
** Globals
*****************************************************************************/

	static USBSUBTYPE xPenPartner[] =
	{
		{ "MODEL_PP_0405", "Wacom PenPartner", 1, 0x00 },
		{ NULL }
	};

	static USBSUBTYPE xGraphire[] =
	{
		{ "ET_0405", "Wacom Graphire", 1, 0x10 },
		{ NULL }
	};

	static USBSUBTYPE xGraphire2[] =
	{
		{ "ET_0405", "Wacom Graphire2 4x5", 1, 0x11 },
		{ "ET_0507", "Wacom Graphire2 5x7", 1, 0x12 },
		{ NULL }
	};

	static USBSUBTYPE xGraphire3[] =
	{
		{ "ET_0405", "Wacom Graphire3 4x5", 1, 0x13 },
		{ "ET_0608", "Wacom Graphire3 6x8", 1, 0x14 },
		{ NULL }
	};

	static USBSUBTYPE xIntuos[] =
	{
		{ "GD_0405-U", "Wacom Intuos 4x5", 1, 0x20 },
		{ "GD_0608-U", "Wacom Intuos 6x8", 2, 0x21 },
		{ "GD_0912-U", "Wacom Intuos 9x12", 3, 0x22 },
		{ "GD_1212-U", "Wacom Intuos 12x12", 4, 0x23 },
		{ "GD_1218-U", "Wacom Intuos 12x18", 5, 0x24 },
		{ NULL }
	};

	static USBSUBTYPE xCintiq[] =
	{
		{ "MODEL_PL400", "Wacom PL400", 1, 0x30 },
		{ "MODEL_PL500", "Wacom PL500", 2, 0x31 },
		{ "MODEL_PL600", "Wacom PL600", 3, 0x32 },
		{ "MODEL_PL600SX", "Wacom PL600SX", 4, 0x33 },
		{ "MODEL_PL550", "Wacom PL550", 5, 0x34 },
		{ "MODEL_PL800", "Wacom PL800", 6, 0x35 },
		{ NULL }
	};

	static USBSUBTYPE xIntuos2[] =
	{
		{ "XD-0405-U", "Wacom Intuos2 4x5",   1, 0x41 },
		{ "XD-0608-U", "Wacom Intuos2 6x8",   2, 0x42 },
		{ "XD-0912-U", "Wacom Intuos2 9x12",  3, 0x43 },
		{ "XD-1212-U", "Wacom Intuos2 12x12", 4, 0x44 },
		{ "XD-1218-U", "Wacom Intuos2 12x18", 5, 0x45 },

		/* fix for I2 6x8's reporting as 0x47 */
		{ "XD-0608-U", "Wacom Intuos2 6x8", 2, 0x47 },

		{ NULL }
	};

	static USBSUBTYPE xVolito[] =
	{
		{ "MODEL-VOL", "Wacom Volito", 1, 0x60 },
		{ NULL }
	};

	static USBSUBTYPE xCintiqPartner[] =
	{
		{ "PTU-600", "Wacom Cintiq Partner", 1, 0x03 },
		{ NULL }
	};

	static USBSUBTYPE xIntuos3[] =
	{
		{ "PTZ-430", "Wacom Intuos3 4x5",  1, 0xB0 },
		{ "PTZ-630", "Wacom Intuos3 6x8",  2, 0xB1 },
		{ "PTZ-930", "Wacom Intuos3 9x12", 3, 0xB2 },
		{ NULL }
	};

	static USBDEVICE xWacomDevices[] =
	{
		{ "pp", "PenPartner", WACOMDEVICE_PENPARTNER, xPenPartner },
		{ "gr", "Graphire", WACOMDEVICE_GRAPHIRE, xGraphire },
		{ "gr2", "Graphire2", WACOMDEVICE_GRAPHIRE2, xGraphire2 },
		{ "gr3", "Graphire3", WACOMDEVICE_GRAPHIRE3, xGraphire3 },
		{ "int", "Intuos", WACOMDEVICE_INTUOS, xIntuos },
		{ "int2", "Intuos2", WACOMDEVICE_INTUOS2, xIntuos2 },
		{ "int3", "Intuos3", WACOMDEVICE_INTUOS3, xIntuos3 },
		{ "pl", "Cintiq (PL)", WACOMDEVICE_CINTIQ, xCintiq },
		{ "ptu", "Cintiq Partner (PTU)", WACOMDEVICE_PTU, xCintiqPartner },
		{ "vol", "Volito", WACOMDEVICE_VOLITO, xVolito },
		{ NULL }
	};

	static USBVENDOR xVendors[] =
	{
		{ "wacom", "Wacom", WACOMVENDOR_WACOM, xWacomDevices },
		{ NULL },
	};

/*****************************************************************************
** Public Functions
*****************************************************************************/

typedef struct
{
	void (*pfnFree)(void* pv);
} DEVLIST_INTERNAL;

static void USBFreeDeviceList(void* pv)
{
	DEVLIST_INTERNAL* pInt = ((DEVLIST_INTERNAL*)pv) - 1;
	free(pInt);
}

int WacomGetSupportedUSBDeviceList(WACOMDEVICEREC** ppList, int* pnSize)
{
	int nIndex=0, nCnt=0;
	DEVLIST_INTERNAL* pInt;
	USBDEVICE* pDev;
	USBVENDOR* pVendor;
	WACOMDEVICEREC* pRec;

	if (!ppList || !pnSize) { errno = EINVAL; return 1; }

	/* for each vendor, count up devices */
	for (pVendor=xVendors; pVendor->pszName; ++pVendor)
	{
		/* count up devices */
		for (pDev=pVendor->pDevices; pDev->pszName; ++pDev, ++nCnt) ;
	}
   
	/* allocate enough memory to hold internal structure and all records */
	pInt = (DEVLIST_INTERNAL*)malloc(sizeof(DEVLIST_INTERNAL) +
					(sizeof(WACOMDEVICEREC) * nCnt));

	pInt->pfnFree = USBFreeDeviceList;
	pRec = (WACOMDEVICEREC*)(pInt + 1);

	/* for each vendor, add devices */
	for (pVendor=xVendors; pVendor->pszName; ++pVendor)
	{
		for (pDev=pVendor->pDevices; pDev->pszName; ++pDev, ++nIndex)
		{
			pRec[nIndex].pszName = pDev->pszName;
			pRec[nIndex].pszDesc = pDev->pszDesc;
			pRec[nIndex].pszVendorName = pVendor->pszName;
			pRec[nIndex].pszVendorDesc = pVendor->pszDesc;
			pRec[nIndex].pszClass = "usb";
			pRec[nIndex].model.uClass = WACOMCLASS_USB;
			pRec[nIndex].model.uVendor = pVendor->uVendor;
			pRec[nIndex].model.uDevice = pDev->uDevice;
			pRec[nIndex].model.uSubType = 0;
		}
	}
	assert(nIndex == nCnt);

	*ppList = pRec;
	*pnSize = nCnt;
	return 0;
}

unsigned int WacomGetUSBDeviceFromName(const char* pszName)
{
	USBDEVICE* pDev;
	USBVENDOR* pVendor;

	if (!pszName) { errno = EINVAL; return 0; }

	/* for each vendor, look for device */
	for (pVendor=xVendors; pVendor->pszName; ++pVendor)
	{
		/* count up devices */
		for (pDev=pVendor->pDevices; pDev->pszName; ++pDev)
		{
			if (strcasecmp(pszName,pDev->pszName) == 0)
				return pDev->uDevice;
		}
	}

	errno = ENOENT;
	return 0;
}

WACOMTABLET WacomOpenUSBTablet(WACOMENGINE hEngine, int fd, WACOMMODEL* pModel)
{
	USBTABLET* pUSB = NULL;

	/* Allocate tablet */
	pUSB = (USBTABLET*)malloc(sizeof(USBTABLET));
	memset(pUSB,0,sizeof(*pUSB));
	pUSB->tablet.Close = USBClose;
	pUSB->tablet.GetModel = USBGetModel;
	pUSB->tablet.GetVendorName = USBGetVendorName;
	pUSB->tablet.GetClassName = USBGetClassName;
	pUSB->tablet.GetDeviceName = USBGetDeviceName;
	pUSB->tablet.GetSubTypeName = USBGetSubTypeName;
	pUSB->tablet.GetModelName = USBGetModelName;
	pUSB->tablet.GetROMVer = USBGetROMVer;
	pUSB->tablet.GetCaps = USBGetCaps;
	pUSB->tablet.GetState = USBGetState;
	pUSB->tablet.GetFD = USBGetFD;
	pUSB->tablet.ReadRaw = USBReadRaw;
	pUSB->tablet.ParseData = USBParseData;
	pUSB->hEngine = hEngine;
	pUSB->fd = fd;

	/* Identify and initialize the model */
	if (USBIdentifyModel(pUSB))
	{
		WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR,
			"Failed to identify model: %s",strerror(errno));
		free(pUSB);
		return NULL;
	}

	return (WACOMTABLET)pUSB;
}

/*****************************************************************************
** Tablet Functions
*****************************************************************************/

static void USBClose(WACOMTABLET_PRIV* pTablet)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	close(pUSB->fd);
	free(pUSB);
}

static WACOMMODEL USBGetModel(WACOMTABLET_PRIV* pTablet)
{
	WACOMMODEL model = { 0 };
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	model.uClass = WACOMCLASS_USB;
	model.uVendor = pUSB->pVendor->uVendor;
	model.uDevice = pUSB->pDevice->uDevice;
	model.uSubType = pUSB->pSubType->uSubType;
	return model;
}

static int USBGetRange(USBTABLET* pUSB, unsigned long* pBits, int nAbsField,
		unsigned int uField)
{
	int nAbs[5];

	if  (!ISBITSET(pBits,nAbsField))
		return 0;
	if (ioctl(pUSB->fd, EVIOCGABS(nAbsField), nAbs) != 0)
		return 1;

	pUSB->state.values[uField].nMin = nAbs[1];
	pUSB->state.values[uField].nMax = nAbs[2];
	pUSB->state.uValid |= BIT(uField);
	return 0;
}

static int USBFindModel(USBTABLET* pUSB, unsigned int uVendor,
		unsigned int uProduct)
{
	USBVENDOR* pVendor = NULL;
	USBDEVICE* pDev = NULL;
	USBSUBTYPE* pSub = NULL;

	static USBSUBTYPE xUnkSub[] =
	{
		{ "UNKNOWN", "Unknown", 1, 0x00 },
		{ NULL }
	};

	static USBDEVICE xUnkDev[] =
	{
		{ "unk", "Unknown", WACOMDEVICE_UNKNOWN, xUnkSub },
		{ NULL }
	};

	static USBVENDOR xUnkVendor =
	{ "unknown", "Unknown", WACOMVENDOR_UNKNOWN, xUnkDev };

	for (pVendor=xVendors; pVendor->pszName; ++pVendor)
	{
		if (pVendor->uVendor == uVendor)
		{
			for (pDev=pVendor->pDevices; pDev->pszName; ++pDev)
			{
				for (pSub=pDev->pSubTypes; pSub->pszName; ++pSub)
				{
					if (pSub->uProduct == uProduct)
					{
						pUSB->pVendor = pVendor;
						pUSB->pDevice = pDev;
						pUSB->pSubType = pSub;
						return 0;
					}
				}
			}

			/* only one vendor of this type, so we're done */
			break;
		}
	}

	/* unknown vendor */
	pUSB->pVendor = &xUnkVendor;
	pUSB->pDevice = pUSB->pVendor->pDevices;
	pUSB->pSubType = pUSB->pDevice->pSubTypes;
	return 0;
}



static int USBIdentifyModel(USBTABLET* pUSB)
{
	int nCnt;
	short sID[4];
	unsigned int uVendor, uProduct;
	unsigned long evbits[NBITS(EV_MAX)];
	unsigned long absbits[NBITS(ABS_MAX)];
	unsigned long relbits[NBITS(REL_MAX)];
	unsigned long keybits[NBITS(KEY_MAX)];

	/* Get device name and id */
	if (ioctl(pUSB->fd,EVIOCGID,sID) < 0)
		return 1;

	/* initialize state structure */
	pUSB->state.uValueCnt = WACOMFIELD_MAX;

	/* Get event types supported */
	nCnt = ioctl(pUSB->fd,EVIOCGBIT(0 /*EV*/,sizeof(evbits)),evbits);
	if (nCnt < 0)
	{
		WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR,
			"Failed to CGBIT ev: %s",strerror(errno));
		return 1;
	}
	assert(nCnt == sizeof(evbits));

	/* absolute events */
	if (ISBITSET(evbits,EV_ABS))
	{
		nCnt = ioctl(pUSB->fd,EVIOCGBIT(EV_ABS,sizeof(absbits)),absbits);
		if (nCnt < 0)
		{
			WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR,
				"Failed to CGBIT abs: %s",strerror(errno));
			return 1;
		}

		/* the following line has problem on Debian systems
		assert(nCnt == sizeof(absbits));
		*/

		if (USBGetRange(pUSB,absbits,ABS_X,WACOMFIELD_POSITION_X) ||
			USBGetRange(pUSB,absbits,ABS_Y,WACOMFIELD_POSITION_Y) ||
			USBGetRange(pUSB,absbits,ABS_RZ,WACOMFIELD_ROTATION_Z) ||
			USBGetRange(pUSB,absbits,ABS_DISTANCE,WACOMFIELD_DISTANCE) ||
			USBGetRange(pUSB,absbits,ABS_PRESSURE,WACOMFIELD_PRESSURE) ||
			USBGetRange(pUSB,absbits,ABS_TILT_X,WACOMFIELD_TILT_X) ||
			USBGetRange(pUSB,absbits,ABS_TILT_Y,WACOMFIELD_TILT_Y) ||
			USBGetRange(pUSB,absbits,ABS_WHEEL,WACOMFIELD_ABSWHEEL) ||
			USBGetRange(pUSB,absbits,ABS_THROTTLE,WACOMFIELD_THROTTLE))
			return 1;
	}

	/* relative events */
	if (ISBITSET(evbits,EV_REL))
	{
		nCnt = ioctl(pUSB->fd,EVIOCGBIT(EV_REL,sizeof(relbits)),relbits);
		if (nCnt < 0)
		{
			WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR,
				"Failed to CGBIT rel: %s",strerror(errno));
			return 1;
		}
		assert(nCnt == sizeof(relbits));

		if (ISBITSET(relbits,REL_WHEEL))
		{
			pUSB->state.uValid |= BIT(WACOMFIELD_RELWHEEL);
			pUSB->state.values[WACOMFIELD_RELWHEEL].nMin = -1;
			pUSB->state.values[WACOMFIELD_RELWHEEL].nMax = 1;
		}
	}

	/* key events */
	if (ISBITSET(evbits,EV_KEY))
	{
		nCnt = ioctl(pUSB->fd,EVIOCGBIT(EV_KEY,sizeof(keybits)),keybits);
		if (nCnt < 0)
		{
			WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR,
				"Failed to CGBIT key: %s",strerror(errno));
			return 1;
		}
		assert(nCnt == sizeof(keybits));

		/* button events */
		if (ISBITSET(keybits,BTN_LEFT) ||
				ISBITSET(keybits,BTN_RIGHT) ||
				ISBITSET(keybits,BTN_MIDDLE) ||
				ISBITSET(keybits,BTN_SIDE) ||
				ISBITSET(keybits,BTN_EXTRA))
			pUSB->state.uValid |= BIT(WACOMFIELD_BUTTONS);

		/* tool events */
		if (ISBITSET(keybits,BTN_TOOL_PEN) ||
				ISBITSET(keybits,BTN_TOOL_RUBBER) ||
				ISBITSET(keybits,BTN_TOOL_BRUSH) ||
				ISBITSET(keybits,BTN_TOOL_PENCIL) ||
				ISBITSET(keybits,BTN_TOOL_AIRBRUSH) ||
				ISBITSET(keybits,BTN_TOOL_FINGER) ||
				ISBITSET(keybits,BTN_TOOL_MOUSE) ||
				ISBITSET(keybits,BTN_TOOL_LENS))
			pUSB->state.uValid |= BIT(WACOMFIELD_PROXIMITY) |
					BIT(WACOMFIELD_TOOLTYPE);
	}

	/* set identification */
	pUSB->nBus = sID[0];
	uVendor = sID[1];
	uProduct = sID[2];
	pUSB->nVersion = sID[3];

	if (USBFindModel(pUSB,uVendor,uProduct))
		return 1;

	/* add additional capabilities by device type */
	switch (pUSB->pDevice->uDevice)
	{
		case WACOMDEVICE_INTUOS:
		case WACOMDEVICE_INTUOS2:
			pUSB->state.uValid |= BIT(WACOMFIELD_SERIAL);
		default: ;
	}

	return 0;
}

static const char* USBGetVendorName(WACOMTABLET_PRIV* pTablet)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	return pUSB->pVendor->pszDesc;
}

static const char* USBGetClassName(WACOMTABLET_PRIV* pTablet)
{
	return "USB";
}

static const char* USBGetDeviceName(WACOMTABLET_PRIV* pTablet)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	return pUSB->pDevice->pszDesc;
}

static const char* USBGetSubTypeName(WACOMTABLET_PRIV* pTablet)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	return pUSB->pSubType->pszName;
}

static const char* USBGetModelName(WACOMTABLET_PRIV* pTablet)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	return pUSB->pSubType->pszDesc;
}

static int USBGetROMVer(WACOMTABLET_PRIV* pTablet, int* pnMajor,
		int* pnMinor, int* pnRelease)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	if (!pnMajor) { errno=EINVAL; return 1; }

	/* how is the version number broken down?
	 * mine says 0x115.  is that 1.1.5 or 1.15? */
	*pnMajor = pUSB->nVersion >> 8;
	*pnMinor = (pUSB->nVersion >> 4) & 0xF;
	*pnRelease = (pUSB->nVersion & 0xF);
	return 0;
}

static int USBGetCaps(WACOMTABLET_PRIV* pTablet)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	return pUSB->state.uValid;
}

static int USBGetState(WACOMTABLET_PRIV* pTablet, WACOMSTATE* pState)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	return WacomCopyState(pState,&pUSB->state);
}

static int USBGetFD(WACOMTABLET_PRIV* pTablet)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	return pUSB->fd;
}

static int USBReadRaw(WACOMTABLET_PRIV* pTablet, unsigned char* puchData,
		unsigned int uSize)
{
	int nXfer;
	unsigned int uCnt, uPacketLength;
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	uPacketLength = sizeof(struct input_event);

	/* check size of buffer */
	if (uSize < uPacketLength) { errno=EINVAL; return 0; }
	
	for (uCnt=0; uCnt<uPacketLength; uCnt+=nXfer)
	{
		nXfer = read(pUSB->fd,puchData+uCnt,uPacketLength-uCnt);
		if (nXfer <= 0) return nXfer;
	}

	return (signed)uCnt;
}

static int USBParseMSC(USBTABLET* pUSB, struct input_event* pEv)
{
	if (pEv->code == MSC_SERIAL)
		pUSB->state.values[WACOMFIELD_SERIAL].nValue = pEv->value;
	return 0;
}

static int USBParseKEY(USBTABLET* pUSB, struct input_event* pEv)
{
	int i, button=-1, tool=0;
	switch (pEv->code)
	{
		case BTN_LEFT: button = WACOMBUTTON_LEFT; break;
		case BTN_RIGHT: button = WACOMBUTTON_RIGHT; break;
		case BTN_MIDDLE: button = WACOMBUTTON_MIDDLE; break;
		case BTN_SIDE: button = WACOMBUTTON_SIDE; break;
		case BTN_EXTRA: button = WACOMBUTTON_EXTRA; break;
		case BTN_TOUCH: button = WACOMBUTTON_TOUCH; break;
		case BTN_STYLUS: button = WACOMBUTTON_STYLUS; break;
		case BTN_STYLUS2: button = WACOMBUTTON_STYLUS2; break;
		case BTN_TOOL_PEN: tool = WACOMTOOLTYPE_PEN; break;
		case BTN_TOOL_PENCIL: tool = WACOMTOOLTYPE_PENCIL; break;
		case BTN_TOOL_BRUSH: tool = WACOMTOOLTYPE_BRUSH; break;
		case BTN_TOOL_RUBBER: tool = WACOMTOOLTYPE_ERASER; break;
		case BTN_TOOL_AIRBRUSH: tool = WACOMTOOLTYPE_AIRBRUSH; break;
		case BTN_TOOL_MOUSE: tool = WACOMTOOLTYPE_MOUSE; break;
		case BTN_TOOL_FINGER: break;
		case BTN_TOOL_LENS: tool = WACOMTOOLTYPE_LENS; break;
	}

	/* if button was specified */
	if (button != -1)
	{
		/* button state change */
		if (pEv->value)
			pUSB->state.values[WACOMFIELD_BUTTONS].nValue |= BIT(button);
		else
			pUSB->state.values[WACOMFIELD_BUTTONS].nValue &= ~BIT(button);
	}

	/* if a tool was specified */
	if (tool)
	{
		/* coming into proximity */
		if (pEv->value)
		{
			/* no prior tool in proximity */
			if (!pUSB->nToolProx)
			{
				pUSB->state.values[WACOMFIELD_PROXIMITY].nValue = 1;
				pUSB->state.values[WACOMFIELD_TOOLTYPE].nValue = tool;
			}

			/* remember tool in prox */
			pUSB->nToolProx |= BIT(tool);
		}

		/* otherwise, going out of proximity */
		else
		{
			/* forget tool in prox */
			pUSB->nToolProx &= ~BIT(tool);

			/* nobody left? out of proximity */
			if (!pUSB->nToolProx)
				memset(pUSB->state.values, 0,
						pUSB->state.uValueCnt * sizeof(WACOMVALUE));

			/* otherwise, switch to next tool */
			else
			{
				for (i=WACOMTOOLTYPE_PEN; i<WACOMTOOLTYPE_MAX; ++i)
				{
					if (pUSB->nToolProx & BIT(i))
					{
						pUSB->state.values[WACOMFIELD_TOOLTYPE].nValue = i;
					}
				}
			}
		} /* out of prox */
	} /* end if tool */

	return 0;
}

static int USBParseABS(USBTABLET* pUSB, struct input_event* pEv)
{
	int field = 0;
	switch (pEv->code)
	{
		case ABS_X: field = WACOMFIELD_POSITION_X; break;
		case ABS_Y: field = WACOMFIELD_POSITION_Y; break;
		case ABS_RZ: field = WACOMFIELD_ROTATION_Z; break;
		case ABS_DISTANCE: field = WACOMFIELD_DISTANCE; break;
		case ABS_PRESSURE: field = WACOMFIELD_PRESSURE; break;
		case ABS_TILT_X: field = WACOMFIELD_TILT_X; break;
		case ABS_TILT_Y: field = WACOMFIELD_TILT_Y; break;
		case ABS_WHEEL: field = WACOMFIELD_ABSWHEEL; break;
		case ABS_THROTTLE: field = WACOMFIELD_THROTTLE; break;
	}

	if (field)
		pUSB->state.values[field].nValue = pEv->value;

	return 0;
}

static int USBParseREL(USBTABLET* pUSB, struct input_event* pEv)
{
	int field = 0;
	switch (pEv->code)
	{
		case REL_WHEEL: field = WACOMFIELD_RELWHEEL; break;
	}

	if (field)
		pUSB->state.values[field].nValue = pEv->value;

	return 0;
}

static int USBParseData(WACOMTABLET_PRIV* pTablet,
		const unsigned char* puchData, unsigned int uLength,
		WACOMSTATE* pState)
{
	USBTABLET* pUSB = (USBTABLET*)pTablet;
	struct input_event* pEv = (struct input_event*)puchData;
	if (uLength != sizeof(struct input_event)) return 1;

	pUSB->state.values[WACOMFIELD_RELWHEEL].nValue = 0;

	/* dispatch event */
	switch (pEv->type)
	{
#ifdef EV_SYN
		case EV_SYN: /* kernel 2.6 */
#endif
		case EV_MSC: /* kernel 2.4 */
			     if (USBParseMSC(pUSB,pEv)) return 1; break;
		case EV_KEY: if (USBParseKEY(pUSB,pEv)) return 1; break;
		case EV_ABS: if (USBParseABS(pUSB,pEv)) return 1; break;
		case EV_REL: if (USBParseREL(pUSB,pEv)) return 1; break;
		default: errno = EINVAL; return 1;
	}

	return pState ? WacomCopyState(pState,&pUSB->state) : 0;
}

/*****************************************************************************
** End USB Linux Input Subsystem
*****************************************************************************/

#else /* WCM_ENABLE_LINUXWACOM */

WACOMTABLET WacomOpenUSBTablet(WACOMENGINE hEngine, int fd, WACOMMODEL* pModel)
{
	errno = EPERM;
	return NULL;
}

unsigned int WacomGetUSBDeviceFromName(const char* pszName)
{
	errno = ENOENT;
	return 0;
}

int WacomGetSupportedUSBDeviceList(WACOMDEVICEREC** ppList, int* pnSize)
{
	if (!ppList || !pnSize) { errno = EINVAL; return 1; }
	*ppList = NULL;
	*pnSize = 0;
	return 0;
}

#endif /* WCM_ENABLE_LINUXWACOM */
