root/src/add-ons/kernel/drivers/input/hid_shared/TabletProtocolHandler.cpp
/*
 * Copyright 2013 Stephan Aßmus <superstippi@gmx.de>
 * Copyright 2010-2011 Enrique Medina Gremaldos <quiqueiii@gmail.com>
 * Copyright 2008-2011 Michael Lotz <mmlr@mlotz.ch>
 * 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 <new>
#include <kernel.h>
#include <string.h>
#include <usb/USB_hid.h>

#include <keyboard_mouse_driver.h>


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)
                                        return result;
                                if (result == B_BUSY)
                                        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 (result == B_INTERRUPTED)
                        return result;
                if ((*cookie & PROTOCOL_HANDLER_COOKIE_FLAG_CLOSED) != 0)
                        return B_FILE_ERROR;

                if (result != B_BUSY) {
                        // "busy" happens 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_BUSY;
        }

        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;
}