1/*
2 * Copyright 2008-2011 Michael Lotz <mmlr@mlotz.ch>
3 * Distributed under the terms of the MIT license.
4 */
5
6
7//!	Driver for USB Human Interface Devices.
8
9
10#include "Driver.h"
11#include "MouseProtocolHandler.h"
12
13#include "HIDCollection.h"
14#include "HIDDevice.h"
15#include "HIDReport.h"
16#include "HIDReportItem.h"
17
18#include <new>
19#include <string.h>
20#include <usb/USB_hid.h>
21
22#include <kernel.h>
23#include <keyboard_mouse_driver.h>
24
25
26MouseProtocolHandler::MouseProtocolHandler(HIDReport &report,
27	HIDReportItem &xAxis, HIDReportItem &yAxis)
28	:
29	ProtocolHandler(report.Device(), "input/mouse/" DEVICE_PATH_SUFFIX "/", 0),
30	fReport(report),
31
32	fXAxis(xAxis),
33	fYAxis(yAxis),
34	fWheel(NULL),
35	fHorizontalPan(NULL),
36
37	fLastButtons(0),
38	fClickCount(0),
39	fLastClickTime(0),
40	fClickSpeed(250000)
41{
42	uint32 buttonCount = 0;
43	for (uint32 i = 0; i < report.CountItems(); i++) {
44		HIDReportItem *item = report.ItemAt(i);
45		if (!item->HasData())
46			continue;
47
48		if (item->UsagePage() == B_HID_USAGE_PAGE_BUTTON
49			&& item->UsageID() - 1 < B_MAX_MOUSE_BUTTONS) {
50			fButtons[buttonCount++] = item;
51		}
52	}
53
54	fButtons[buttonCount] = NULL;
55
56	fWheel = report.FindItem(B_HID_USAGE_PAGE_GENERIC_DESKTOP,
57		B_HID_UID_GD_WHEEL);
58	fHorizontalPan = report.FindItem(B_HID_USAGE_PAGE_CONSUMER,
59		B_HID_UID_CON_AC_PAN);
60
61	TRACE("mouse device with %" B_PRIu32 " buttons %sand %swheel\n",
62		buttonCount, fHorizontalPan == NULL ? "" : ", horizontal pan ",
63		fWheel == NULL ? "no " : "");
64	TRACE("report id: %u\n", report.ID());
65}
66
67
68void
69MouseProtocolHandler::AddHandlers(HIDDevice &device, HIDCollection &collection,
70	ProtocolHandler *&handlerList)
71{
72	bool supported = false;
73	switch (collection.UsagePage()) {
74		case B_HID_USAGE_PAGE_GENERIC_DESKTOP:
75		{
76			switch (collection.UsageID()) {
77				case B_HID_UID_GD_MOUSE:
78				case B_HID_UID_GD_POINTER:
79					supported = true;
80					break;
81			}
82
83			break;
84		}
85	}
86
87	if (!supported) {
88		TRACE("collection not a mouse\n");
89		return;
90	}
91
92	HIDParser &parser = device.Parser();
93	uint32 maxReportCount = parser.CountReports(HID_REPORT_TYPE_INPUT);
94	if (maxReportCount == 0)
95		return;
96
97	uint32 inputReportCount = 0;
98	HIDReport *inputReports[maxReportCount];
99	collection.BuildReportList(HID_REPORT_TYPE_INPUT, inputReports,
100		inputReportCount);
101
102	for (uint32 i = 0; i < inputReportCount; i++) {
103		HIDReport *inputReport = inputReports[i];
104
105		// try to find at least an absolute x and y axis
106		HIDReportItem *xAxis = inputReport->FindItem(
107			B_HID_USAGE_PAGE_GENERIC_DESKTOP, B_HID_UID_GD_X);
108		if (xAxis == NULL || !xAxis->Relative())
109			continue;
110
111		HIDReportItem *yAxis = inputReport->FindItem(
112			B_HID_USAGE_PAGE_GENERIC_DESKTOP, B_HID_UID_GD_Y);
113		if (yAxis == NULL || !yAxis->Relative())
114			continue;
115
116		ProtocolHandler *newHandler = new(std::nothrow) MouseProtocolHandler(
117			*inputReport, *xAxis, *yAxis);
118		if (newHandler == NULL) {
119			TRACE("failed to allocated mouse protocol handler\n");
120			continue;
121		}
122
123		newHandler->SetNextHandler(handlerList);
124		handlerList = newHandler;
125	}
126}
127
128
129status_t
130MouseProtocolHandler::Control(uint32 *cookie, uint32 op, void *buffer,
131	size_t length)
132{
133	switch (op) {
134
135		case B_GET_DEVICE_NAME:
136		{
137			const char name[] = DEVICE_NAME" Mouse";
138			return IOGetDeviceName(name,buffer,length);
139		}
140
141		case MS_READ:
142		{
143			if (length < sizeof(mouse_movement))
144				return B_BUFFER_OVERFLOW;
145
146			while (true) {
147				mouse_movement movement;
148				status_t result = _ReadReport(&movement, cookie);
149				if (result == B_INTERRUPTED)
150					continue;
151
152				if (!IS_USER_ADDRESS(buffer)
153					|| user_memcpy(buffer, &movement, sizeof(movement))
154						!= B_OK) {
155					return B_BAD_ADDRESS;
156				}
157
158				return result;
159			}
160		}
161
162		case MS_NUM_EVENTS:
163		{
164			if (fReport.Device()->IsRemoved())
165				return B_DEV_NOT_READY;
166
167			// we are always on demand, so 0 queued events
168			return 0;
169		}
170
171		case MS_SET_CLICKSPEED:
172			if (!IS_USER_ADDRESS(buffer)
173				|| user_memcpy(&fClickSpeed, buffer, sizeof(bigtime_t))
174					!= B_OK) {
175				return B_BAD_ADDRESS;
176			}
177
178			return B_OK;
179	}
180
181	return B_ERROR;
182}
183
184
185status_t
186MouseProtocolHandler::_ReadReport(void *buffer, uint32 *cookie)
187{
188	status_t result = fReport.WaitForReport(B_INFINITE_TIMEOUT);
189	if (result != B_OK) {
190		if (fReport.Device()->IsRemoved()) {
191			TRACE("device has been removed\n");
192			return B_DEV_NOT_READY;
193		}
194
195		if ((*cookie & PROTOCOL_HANDLER_COOKIE_FLAG_CLOSED) != 0)
196			return B_CANCELED;
197
198		if (result != B_INTERRUPTED) {
199			// interrupts happen when other reports come in on the same
200			// input as ours
201			TRACE_ALWAYS("error waiting for report: %s\n", strerror(result));
202		}
203
204		// signal that we simply want to try again
205		return B_INTERRUPTED;
206	}
207
208	uint32 axisRelativeData[2];
209	axisRelativeData[0] = 0;
210	axisRelativeData[1] = 0;
211
212	if (fXAxis.Extract() == B_OK && fXAxis.Valid())
213		axisRelativeData[0] = fXAxis.Data();
214
215	if (fYAxis.Extract() == B_OK && fYAxis.Valid())
216		axisRelativeData[1] = fYAxis.Data();
217
218	uint32 wheelData[2] = {0};
219	if (fWheel != NULL && fWheel->Extract() == B_OK && fWheel->Valid())
220		wheelData[0] = fWheel->Data();
221	if (fHorizontalPan != NULL && fHorizontalPan->Extract() == B_OK
222			&& fHorizontalPan->Valid())
223		wheelData[1] = fHorizontalPan->Data();
224
225	uint32 buttons = 0;
226	for (uint32 i = 0; i < B_MAX_MOUSE_BUTTONS; i++) {
227		HIDReportItem *button = fButtons[i];
228		if (button == NULL)
229			break;
230
231		if (button->Extract() == B_OK && button->Valid())
232			buttons |= (button->Data() & 1) << (button->UsageID() - 1);
233	}
234
235	fReport.DoneProcessing();
236	TRACE("got mouse report\n");
237
238	int32 clicks = 0;
239	bigtime_t timestamp = system_time();
240	if (buttons != 0) {
241		if (fLastButtons == 0) {
242			if (fLastClickTime + fClickSpeed > timestamp)
243				fClickCount++;
244			else
245				fClickCount = 1;
246		}
247
248		fLastClickTime = timestamp;
249		clicks = fClickCount;
250	}
251
252	fLastButtons = buttons;
253
254	mouse_movement *info = (mouse_movement *)buffer;
255	memset(info, 0, sizeof(mouse_movement));
256
257	info->buttons = buttons;
258	info->xdelta = axisRelativeData[0];
259	info->ydelta = -axisRelativeData[1];
260	info->clicks = clicks;
261	info->timestamp = timestamp;
262	info->wheel_ydelta = -wheelData[0];
263	info->wheel_xdelta = wheelData[1];
264
265	return B_OK;
266}
267