1/*
2 * Copyright 2013 Stephan A��mus <superstippi@gmx.de>
3 * Copyright 2010-2011 Enrique Medina Gremaldos <quiqueiii@gmail.com>
4 * Copyright 2008-2011 Michael Lotz <mmlr@mlotz.ch>
5 * Distributed under the terms of the MIT license.
6 */
7
8
9//!	Driver for USB Human Interface Devices.
10
11
12#include "Driver.h"
13#include "TabletProtocolHandler.h"
14
15#include "HIDCollection.h"
16#include "HIDDevice.h"
17#include "HIDReport.h"
18#include "HIDReportItem.h"
19
20#include <new>
21#include <kernel.h>
22#include <string.h>
23#include <usb/USB_hid.h>
24
25#include <keyboard_mouse_driver.h>
26
27
28TabletProtocolHandler::TabletProtocolHandler(HIDReport &report,
29	HIDReportItem &xAxis, HIDReportItem &yAxis)
30	:
31	ProtocolHandler(report.Device(), "input/tablet/" DEVICE_PATH_SUFFIX "/",
32		0),
33	fReport(report),
34
35	fXAxis(xAxis),
36	fYAxis(yAxis),
37	fWheel(NULL),
38
39	fPressure(NULL),
40	fInRange(NULL),
41	fXTilt(NULL),
42	fYTilt(NULL),
43
44	fLastButtons(0),
45	fLastSwitches(0),
46	fClickCount(0),
47	fLastClickTime(0),
48	fClickSpeed(250000)
49{
50	uint32 buttonCount = 0;
51	uint32 switchCount = 0;
52
53	for (uint32 i = 0; i < report.CountItems(); i++) {
54		HIDReportItem *item = report.ItemAt(i);
55		if (!item->HasData())
56			continue;
57
58		if (item->UsagePage() == B_HID_USAGE_PAGE_BUTTON
59			&& item->UsageID() - 1 < B_MAX_MOUSE_BUTTONS) {
60			fButtons[buttonCount++] = item;
61		}
62
63		if (item->UsagePage() == B_HID_USAGE_PAGE_DIGITIZER
64			&& item->UsageID() >= B_HID_UID_DIG_TIP_SWITCH
65			&& item->UsageID() <= B_HID_UID_DIG_TABLET_PICK) {
66			fSwitches[switchCount++] = item;
67		}
68	}
69
70	fButtons[buttonCount] = NULL;
71	fSwitches[switchCount] = NULL;
72
73	fWheel = report.FindItem(B_HID_USAGE_PAGE_GENERIC_DESKTOP,
74		B_HID_UID_GD_WHEEL);
75
76	fPressure = report.FindItem(B_HID_USAGE_PAGE_DIGITIZER,
77		B_HID_UID_DIG_TIP_PRESSURE);
78
79	fInRange = report.FindItem(B_HID_USAGE_PAGE_DIGITIZER,
80		B_HID_UID_DIG_IN_RANGE);
81
82	fXTilt = report.FindItem(B_HID_USAGE_PAGE_DIGITIZER,
83		B_HID_UID_DIG_X_TILT);
84
85	fYTilt = report.FindItem(B_HID_USAGE_PAGE_DIGITIZER,
86		B_HID_UID_DIG_Y_TILT);
87
88	TRACE("tablet device with %" B_PRIu32 " buttons, %" B_PRIu32 " switches,"
89		"%spressure, and %stilt\n",
90		buttonCount,
91		switchCount,
92		fPressure == NULL ? "no " : "",
93		fXTilt == NULL && fYTilt == NULL ? "no " : "");
94
95	TRACE("report id: %u\n", report.ID());
96}
97
98
99void
100TabletProtocolHandler::AddHandlers(HIDDevice &device, HIDCollection &collection,
101	ProtocolHandler *&handlerList)
102{
103	bool supported = false;
104	switch (collection.UsagePage()) {
105		case B_HID_USAGE_PAGE_GENERIC_DESKTOP:
106		{
107			switch (collection.UsageID()) {
108				case B_HID_UID_GD_MOUSE:
109				case B_HID_UID_GD_POINTER:
110					// NOTE: Maybe it is supported if X-axis and Y-axis are
111					// absolute. This is determined below by scanning the
112					// report items for absolute X and Y axis.
113					supported = true;
114					break;
115			}
116
117			break;
118		}
119
120		case B_HID_USAGE_PAGE_DIGITIZER:
121		{
122			switch (collection.UsageID()) {
123				case B_HID_UID_DIG_DIGITIZER:
124				case B_HID_UID_DIG_PEN:
125				case B_HID_UID_DIG_LIGHT_PEN:
126				case B_HID_UID_DIG_TOUCH_SCREEN:
127				case B_HID_UID_DIG_TOUCH_PAD:
128				case B_HID_UID_DIG_WHITE_BOARD:
129					TRACE("found tablet/digitizer\n");
130					supported = true;
131					break;
132			}
133
134			break;
135		}
136	}
137
138	if (!supported) {
139		TRACE("collection not a tablet/digitizer\n");
140		return;
141	}
142
143	HIDParser &parser = device.Parser();
144	uint32 maxReportCount = parser.CountReports(HID_REPORT_TYPE_INPUT);
145	if (maxReportCount == 0)
146		return;
147
148	uint32 inputReportCount = 0;
149	HIDReport *inputReports[maxReportCount];
150	collection.BuildReportList(HID_REPORT_TYPE_INPUT, inputReports,
151		inputReportCount);
152
153	for (uint32 i = 0; i < inputReportCount; i++) {
154		HIDReport *inputReport = inputReports[i];
155
156		// try to find at least an absolute x and y axis
157		HIDReportItem *xAxis = inputReport->FindItem(
158			B_HID_USAGE_PAGE_GENERIC_DESKTOP, B_HID_UID_GD_X);
159		if (xAxis == NULL || xAxis->Relative())
160			continue;
161
162		HIDReportItem *yAxis = inputReport->FindItem(
163			B_HID_USAGE_PAGE_GENERIC_DESKTOP, B_HID_UID_GD_Y);
164		if (yAxis == NULL || yAxis->Relative())
165			continue;
166
167		ProtocolHandler *newHandler = new(std::nothrow) TabletProtocolHandler(
168			*inputReport, *xAxis, *yAxis);
169		if (newHandler == NULL) {
170			TRACE("failed to allocated tablet protocol handler\n");
171			continue;
172		}
173
174		newHandler->SetNextHandler(handlerList);
175		handlerList = newHandler;
176	}
177}
178
179
180status_t
181TabletProtocolHandler::Control(uint32 *cookie, uint32 op, void *buffer,
182	size_t length)
183{
184	switch (op) {
185
186		case B_GET_DEVICE_NAME:
187		{
188			const char name[] = DEVICE_NAME" Tablet";
189			return IOGetDeviceName(name,buffer,length);
190		}
191
192		case MS_READ:
193		{
194			if (length < sizeof(tablet_movement))
195				return B_BUFFER_OVERFLOW;
196
197			while (true) {
198				tablet_movement movement;
199				status_t result = _ReadReport(&movement, cookie);
200				if (result == B_INTERRUPTED)
201					continue;
202				if (!IS_USER_ADDRESS(buffer)
203					|| user_memcpy(buffer, &movement, sizeof(movement))
204						!= B_OK) {
205					return B_BAD_ADDRESS;
206				}
207
208				return result;
209			}
210		}
211
212		case MS_NUM_EVENTS:
213		{
214			if (fReport.Device()->IsRemoved())
215				return B_DEV_NOT_READY;
216
217			// we are always on demand, so 0 queued events
218			return 0;
219		}
220
221		case MS_SET_CLICKSPEED:
222				if (!IS_USER_ADDRESS(buffer)
223					|| user_memcpy(&fClickSpeed, buffer, sizeof(bigtime_t))
224						!= B_OK) {
225					return B_BAD_ADDRESS;
226				}
227
228				return B_OK;
229	}
230
231	return B_ERROR;
232}
233
234
235status_t
236TabletProtocolHandler::_ReadReport(void *buffer, uint32 *cookie)
237{
238	status_t result = fReport.WaitForReport(B_INFINITE_TIMEOUT);
239	if (result != B_OK) {
240		if (fReport.Device()->IsRemoved()) {
241			TRACE("device has been removed\n");
242			return B_DEV_NOT_READY;
243		}
244
245		if ((*cookie & PROTOCOL_HANDLER_COOKIE_FLAG_CLOSED) != 0)
246			return B_CANCELED;
247
248		if (result != B_INTERRUPTED) {
249			// interrupts happen when other reports come in on the same
250			// input as ours
251			TRACE_ALWAYS("error waiting for report: %s\n", strerror(result));
252		}
253
254		// signal that we simply want to try again
255		return B_INTERRUPTED;
256	}
257
258	float axisAbsoluteData[2];
259	axisAbsoluteData[0] = 0.0f;
260	axisAbsoluteData[1] = 0.0f;
261
262	if (fXAxis.Extract() == B_OK && fXAxis.Valid())
263		axisAbsoluteData[0] = fXAxis.ScaledFloatData();
264
265	if (fYAxis.Extract() == B_OK && fYAxis.Valid())
266		axisAbsoluteData[1] = fYAxis.ScaledFloatData();
267
268	uint32 wheelData = 0;
269	if (fWheel != NULL && fWheel->Extract() == B_OK && fWheel->Valid())
270		wheelData = fWheel->Data();
271
272	uint32 buttons = 0;
273	for (uint32 i = 0; i < B_MAX_MOUSE_BUTTONS; i++) {
274		HIDReportItem *button = fButtons[i];
275		if (button == NULL)
276			break;
277
278		if (button->Extract() == B_OK && button->Valid())
279			buttons |= (button->Data() & 1) << (button->UsageID() - 1);
280	}
281
282	uint32 switches = 0;
283	for (uint32 i = 0; i < B_MAX_DIGITIZER_SWITCHES; i++) {
284		HIDReportItem *dswitch = fSwitches[i];
285		if (dswitch == NULL)
286			break;
287
288		if (dswitch->Extract() == B_OK && dswitch->Valid())
289			switches |= (dswitch->Data() & 1) << (dswitch->UsageID()
290				- B_HID_UID_DIG_TIP_SWITCH);
291	}
292
293	float pressure = 1.0f;
294	if (fPressure != NULL && fPressure->Extract() == B_OK
295		&& fPressure->Valid()) {
296		pressure = fPressure->ScaledFloatData();
297	}
298
299	float xTilt = 0.0f;
300	if (fXTilt != NULL && fXTilt->Extract() == B_OK && fXTilt->Valid())
301		xTilt = fXTilt->ScaledFloatData();
302
303	float yTilt = 0.0f;
304	if (fYTilt != NULL && fYTilt->Extract() == B_OK && fYTilt->Valid())
305		yTilt = fYTilt->ScaledFloatData();
306
307	bool inRange = true;
308	if (fInRange != NULL && fInRange->Extract() == B_OK && fInRange->Valid())
309		inRange = ((fInRange->Data() & 1) != 0);
310
311	fReport.DoneProcessing();
312	TRACE("got tablet report\n");
313
314	int32 clicks = 0;
315	bigtime_t timestamp = system_time();
316	if (buttons != 0) {
317		if (fLastButtons == 0) {
318			if (fLastClickTime + fClickSpeed > timestamp)
319				fClickCount++;
320			else
321				fClickCount = 1;
322		}
323
324		fLastClickTime = timestamp;
325		clicks = fClickCount;
326	}
327
328	fLastButtons = buttons;
329
330	if (switches != 0) {
331		if (fLastSwitches == 0) {
332			if (fLastClickTime + fClickSpeed > timestamp)
333				fClickCount++;
334			else
335				fClickCount = 1;
336		}
337	}
338
339	fLastSwitches = switches;
340
341	tablet_movement *info = (tablet_movement *)buffer;
342	memset(info, 0, sizeof(tablet_movement));
343
344	info->xpos = axisAbsoluteData[0];
345	info->ypos = axisAbsoluteData[1];
346	info->has_contact = inRange;
347	info->pressure = pressure;
348	info->tilt_x = xTilt;
349	info->tilt_y = yTilt;
350
351	info->switches = switches;
352	info->buttons = buttons;
353	info->clicks = clicks;
354	info->timestamp = timestamp;
355	info->wheel_ydelta = -wheelData;
356
357	return B_OK;
358}
359