/* * Copyright 2013 Stephan Aßmus * Copyright 2010-2011 Enrique Medina Gremaldos * Copyright 2008-2011 Michael Lotz * Distributed under the terms of the MIT license. */ //! Driver for USB Human Interface Devices. #include "Driver.h" #include "TabletProtocolHandler.h" #include "HIDCollection.h" #include "HIDDevice.h" #include "HIDReport.h" #include "HIDReportItem.h" #include #include #include #include #include TabletProtocolHandler::TabletProtocolHandler(HIDReport &report, HIDReportItem &xAxis, HIDReportItem &yAxis) : ProtocolHandler(report.Device(), "input/tablet/" DEVICE_PATH_SUFFIX "/", 0), fReport(report), fXAxis(xAxis), fYAxis(yAxis), fWheel(NULL), fPressure(NULL), fInRange(NULL), fXTilt(NULL), fYTilt(NULL), fLastButtons(0), fLastSwitches(0), fClickCount(0), fLastClickTime(0), fClickSpeed(250000) { uint32 buttonCount = 0; uint32 switchCount = 0; for (uint32 i = 0; i < report.CountItems(); i++) { HIDReportItem *item = report.ItemAt(i); if (!item->HasData()) continue; if (item->UsagePage() == B_HID_USAGE_PAGE_BUTTON && item->UsageID() - 1 < B_MAX_MOUSE_BUTTONS) { fButtons[buttonCount++] = item; } if (item->UsagePage() == B_HID_USAGE_PAGE_DIGITIZER && item->UsageID() >= B_HID_UID_DIG_TIP_SWITCH && item->UsageID() <= B_HID_UID_DIG_TABLET_PICK) { fSwitches[switchCount++] = item; } } fButtons[buttonCount] = NULL; fSwitches[switchCount] = NULL; fWheel = report.FindItem(B_HID_USAGE_PAGE_GENERIC_DESKTOP, B_HID_UID_GD_WHEEL); fPressure = report.FindItem(B_HID_USAGE_PAGE_DIGITIZER, B_HID_UID_DIG_TIP_PRESSURE); fInRange = report.FindItem(B_HID_USAGE_PAGE_DIGITIZER, B_HID_UID_DIG_IN_RANGE); fXTilt = report.FindItem(B_HID_USAGE_PAGE_DIGITIZER, B_HID_UID_DIG_X_TILT); fYTilt = report.FindItem(B_HID_USAGE_PAGE_DIGITIZER, B_HID_UID_DIG_Y_TILT); TRACE("tablet device with %" B_PRIu32 " buttons, %" B_PRIu32 " switches," "%spressure, and %stilt\n", buttonCount, switchCount, fPressure == NULL ? "no " : "", fXTilt == NULL && fYTilt == NULL ? "no " : ""); TRACE("report id: %u\n", report.ID()); } void TabletProtocolHandler::AddHandlers(HIDDevice &device, HIDCollection &collection, ProtocolHandler *&handlerList) { bool supported = false; switch (collection.UsagePage()) { case B_HID_USAGE_PAGE_GENERIC_DESKTOP: { switch (collection.UsageID()) { case B_HID_UID_GD_MOUSE: case B_HID_UID_GD_POINTER: // NOTE: Maybe it is supported if X-axis and Y-axis are // absolute. This is determined below by scanning the // report items for absolute X and Y axis. supported = true; break; } break; } case B_HID_USAGE_PAGE_DIGITIZER: { switch (collection.UsageID()) { case B_HID_UID_DIG_DIGITIZER: case B_HID_UID_DIG_PEN: case B_HID_UID_DIG_LIGHT_PEN: case B_HID_UID_DIG_TOUCH_SCREEN: case B_HID_UID_DIG_TOUCH_PAD: case B_HID_UID_DIG_WHITE_BOARD: TRACE("found tablet/digitizer\n"); supported = true; break; } break; } } if (!supported) { TRACE("collection not a tablet/digitizer\n"); return; } HIDParser &parser = device.Parser(); uint32 maxReportCount = parser.CountReports(HID_REPORT_TYPE_INPUT); if (maxReportCount == 0) return; uint32 inputReportCount = 0; HIDReport *inputReports[maxReportCount]; collection.BuildReportList(HID_REPORT_TYPE_INPUT, inputReports, inputReportCount); for (uint32 i = 0; i < inputReportCount; i++) { HIDReport *inputReport = inputReports[i]; // try to find at least an absolute x and y axis HIDReportItem *xAxis = inputReport->FindItem( B_HID_USAGE_PAGE_GENERIC_DESKTOP, B_HID_UID_GD_X); if (xAxis == NULL || xAxis->Relative()) continue; HIDReportItem *yAxis = inputReport->FindItem( B_HID_USAGE_PAGE_GENERIC_DESKTOP, B_HID_UID_GD_Y); if (yAxis == NULL || yAxis->Relative()) continue; ProtocolHandler *newHandler = new(std::nothrow) TabletProtocolHandler( *inputReport, *xAxis, *yAxis); if (newHandler == NULL) { TRACE("failed to allocated tablet protocol handler\n"); continue; } newHandler->SetNextHandler(handlerList); handlerList = newHandler; } } status_t TabletProtocolHandler::Control(uint32 *cookie, uint32 op, void *buffer, size_t length) { switch (op) { case B_GET_DEVICE_NAME: { const char name[] = DEVICE_NAME" Tablet"; return IOGetDeviceName(name,buffer,length); } case MS_READ: { if (length < sizeof(tablet_movement)) return B_BUFFER_OVERFLOW; while (true) { tablet_movement movement; status_t result = _ReadReport(&movement, cookie); if (result == B_INTERRUPTED) continue; if (!IS_USER_ADDRESS(buffer) || user_memcpy(buffer, &movement, sizeof(movement)) != B_OK) { return B_BAD_ADDRESS; } return result; } } case MS_NUM_EVENTS: { if (fReport.Device()->IsRemoved()) return B_DEV_NOT_READY; // we are always on demand, so 0 queued events return 0; } case MS_SET_CLICKSPEED: if (!IS_USER_ADDRESS(buffer) || user_memcpy(&fClickSpeed, buffer, sizeof(bigtime_t)) != B_OK) { return B_BAD_ADDRESS; } return B_OK; } return B_ERROR; } status_t TabletProtocolHandler::_ReadReport(void *buffer, uint32 *cookie) { status_t result = fReport.WaitForReport(B_INFINITE_TIMEOUT); if (result != B_OK) { if (fReport.Device()->IsRemoved()) { TRACE("device has been removed\n"); return B_DEV_NOT_READY; } if ((*cookie & PROTOCOL_HANDLER_COOKIE_FLAG_CLOSED) != 0) return B_CANCELED; if (result != B_INTERRUPTED) { // interrupts happen when other reports come in on the same // input as ours TRACE_ALWAYS("error waiting for report: %s\n", strerror(result)); } // signal that we simply want to try again return B_INTERRUPTED; } float axisAbsoluteData[2]; axisAbsoluteData[0] = 0.0f; axisAbsoluteData[1] = 0.0f; if (fXAxis.Extract() == B_OK && fXAxis.Valid()) axisAbsoluteData[0] = fXAxis.ScaledFloatData(); if (fYAxis.Extract() == B_OK && fYAxis.Valid()) axisAbsoluteData[1] = fYAxis.ScaledFloatData(); uint32 wheelData = 0; if (fWheel != NULL && fWheel->Extract() == B_OK && fWheel->Valid()) wheelData = fWheel->Data(); uint32 buttons = 0; for (uint32 i = 0; i < B_MAX_MOUSE_BUTTONS; i++) { HIDReportItem *button = fButtons[i]; if (button == NULL) break; if (button->Extract() == B_OK && button->Valid()) buttons |= (button->Data() & 1) << (button->UsageID() - 1); } uint32 switches = 0; for (uint32 i = 0; i < B_MAX_DIGITIZER_SWITCHES; i++) { HIDReportItem *dswitch = fSwitches[i]; if (dswitch == NULL) break; if (dswitch->Extract() == B_OK && dswitch->Valid()) switches |= (dswitch->Data() & 1) << (dswitch->UsageID() - B_HID_UID_DIG_TIP_SWITCH); } float pressure = 1.0f; if (fPressure != NULL && fPressure->Extract() == B_OK && fPressure->Valid()) { pressure = fPressure->ScaledFloatData(); } float xTilt = 0.0f; if (fXTilt != NULL && fXTilt->Extract() == B_OK && fXTilt->Valid()) xTilt = fXTilt->ScaledFloatData(); float yTilt = 0.0f; if (fYTilt != NULL && fYTilt->Extract() == B_OK && fYTilt->Valid()) yTilt = fYTilt->ScaledFloatData(); bool inRange = true; if (fInRange != NULL && fInRange->Extract() == B_OK && fInRange->Valid()) inRange = ((fInRange->Data() & 1) != 0); fReport.DoneProcessing(); TRACE("got tablet report\n"); int32 clicks = 0; bigtime_t timestamp = system_time(); if (buttons != 0) { if (fLastButtons == 0) { if (fLastClickTime + fClickSpeed > timestamp) fClickCount++; else fClickCount = 1; } fLastClickTime = timestamp; clicks = fClickCount; } fLastButtons = buttons; if (switches != 0) { if (fLastSwitches == 0) { if (fLastClickTime + fClickSpeed > timestamp) fClickCount++; else fClickCount = 1; } } fLastSwitches = switches; tablet_movement *info = (tablet_movement *)buffer; memset(info, 0, sizeof(tablet_movement)); info->xpos = axisAbsoluteData[0]; info->ypos = axisAbsoluteData[1]; info->has_contact = inRange; info->pressure = pressure; info->tilt_x = xTilt; info->tilt_y = yTilt; info->switches = switches; info->buttons = buttons; info->clicks = clicks; info->timestamp = timestamp; info->wheel_ydelta = -wheelData; return B_OK; }