1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "MouseInputMapper"
18//#define LOG_NDEBUG 0
19
20#include "MouseInputMapper.h"
21
22#include <linux/input.h>
23#include <hardware/input.h>
24#include <utils/Log.h>
25#include <utils/misc.h>
26
27#include "InputHost.h"
28#include "InputHub.h"
29
30
31namespace android {
32
33// Map scancodes to input HAL usages.
34// The order of these definitions MUST remain in sync with the order they are
35// defined in linux/input.h.
36static struct {
37    int32_t scancode;
38    InputUsage usage;
39} codeMap[] = {
40    {BTN_LEFT, INPUT_USAGE_BUTTON_PRIMARY},
41    {BTN_RIGHT, INPUT_USAGE_BUTTON_SECONDARY},
42    {BTN_MIDDLE, INPUT_USAGE_BUTTON_TERTIARY},
43    {BTN_SIDE, INPUT_USAGE_BUTTON_UNKNOWN},
44    {BTN_EXTRA, INPUT_USAGE_BUTTON_UNKNOWN},
45    {BTN_FORWARD, INPUT_USAGE_BUTTON_FORWARD},
46    {BTN_BACK, INPUT_USAGE_BUTTON_BACK},
47    {BTN_TASK, INPUT_USAGE_BUTTON_UNKNOWN},
48};
49
50
51bool MouseInputMapper::configureInputReport(InputDeviceNode* devNode,
52        InputReportDefinition* report) {
53    setInputReportDefinition(report);
54    getInputReportDefinition()->addCollection(INPUT_COLLECTION_ID_MOUSE, 1);
55
56    // Configure mouse axes
57    if (!devNode->hasRelativeAxis(REL_X) || !devNode->hasRelativeAxis(REL_Y)) {
58        ALOGE("Device %s is missing a relative x or y axis. Device cannot be configured.",
59                devNode->getPath().c_str());
60        return false;
61    }
62    getInputReportDefinition()->declareUsage(INPUT_COLLECTION_ID_MOUSE, INPUT_USAGE_AXIS_X,
63            INT32_MIN, INT32_MAX, 1.0f);
64    getInputReportDefinition()->declareUsage(INPUT_COLLECTION_ID_MOUSE, INPUT_USAGE_AXIS_Y,
65            INT32_MIN, INT32_MAX, 1.0f);
66    if (devNode->hasRelativeAxis(REL_WHEEL)) {
67        getInputReportDefinition()->declareUsage(INPUT_COLLECTION_ID_MOUSE,
68                INPUT_USAGE_AXIS_VSCROLL, -1, 1, 0.0f);
69    }
70    if (devNode->hasRelativeAxis(REL_HWHEEL)) {
71        getInputReportDefinition()->declareUsage(INPUT_COLLECTION_ID_MOUSE,
72                INPUT_USAGE_AXIS_HSCROLL, -1, 1, 0.0f);
73    }
74
75    // Configure mouse buttons
76    InputUsage usages[NELEM(codeMap)];
77    int numUsages = 0;
78    for (int32_t i = 0; i < NELEM(codeMap); ++i) {
79        if (devNode->hasKey(codeMap[i].scancode)) {
80            usages[numUsages++] = codeMap[i].usage;
81        }
82    }
83    if (numUsages == 0) {
84        ALOGW("MouseInputMapper found no buttons for %s", devNode->getPath().c_str());
85    }
86    getInputReportDefinition()->declareUsages(INPUT_COLLECTION_ID_MOUSE, usages, numUsages);
87    return true;
88}
89
90void MouseInputMapper::process(const InputEvent& event) {
91    ALOGV("processing mouse event. type=%d code=%d value=%d",
92            event.type, event.code, event.value);
93    switch (event.type) {
94        case EV_KEY:
95            processButton(event.code, event.value);
96            break;
97        case EV_REL:
98            processMotion(event.code, event.value);
99            break;
100        case EV_SYN:
101            if (event.code == SYN_REPORT) {
102                sync(event.when);
103            }
104            break;
105        default:
106            ALOGV("unknown mouse event type: %d", event.type);
107    }
108}
109
110void MouseInputMapper::processMotion(int32_t code, int32_t value) {
111    switch (code) {
112        case REL_X:
113            mRelX = value;
114            break;
115        case REL_Y:
116            mRelY = value;
117            break;
118        case REL_WHEEL:
119            mRelWheel = value;
120            break;
121        case REL_HWHEEL:
122            mRelHWheel = value;
123            break;
124        default:
125            // Unknown code. Ignore.
126            break;
127    }
128}
129
130// Map evdev button codes to bit indices. This function assumes code >=
131// BTN_MOUSE.
132uint32_t buttonToBit(int32_t code) {
133    return static_cast<uint32_t>(code - BTN_MOUSE);
134}
135
136void MouseInputMapper::processButton(int32_t code, int32_t value) {
137    // Mouse buttons start at BTN_MOUSE and end before BTN_JOYSTICK. There isn't
138    // really enough room after the mouse buttons for another button class, so
139    // the risk of a button type being inserted after mouse is low.
140    if (code >= BTN_MOUSE && code < BTN_JOYSTICK) {
141        if (value) {
142            mButtonValues.markBit(buttonToBit(code));
143        } else {
144            mButtonValues.clearBit(buttonToBit(code));
145        }
146        mUpdatedButtonMask.markBit(buttonToBit(code));
147    }
148}
149
150void MouseInputMapper::sync(nsecs_t when) {
151    // Process updated button states.
152    while (!mUpdatedButtonMask.isEmpty()) {
153        auto bit = mUpdatedButtonMask.clearFirstMarkedBit();
154        getInputReport()->setBoolUsage(INPUT_COLLECTION_ID_MOUSE, codeMap[bit].usage,
155                mButtonValues.hasBit(bit), 0);
156    }
157
158    // Process motion and scroll changes.
159    if (mRelX != 0) {
160        getInputReport()->setIntUsage(INPUT_COLLECTION_ID_MOUSE, INPUT_USAGE_AXIS_X, mRelX, 0);
161    }
162    if (mRelY != 0) {
163        getInputReport()->setIntUsage(INPUT_COLLECTION_ID_MOUSE, INPUT_USAGE_AXIS_Y, mRelY, 0);
164    }
165    if (mRelWheel != 0) {
166        getInputReport()->setIntUsage(INPUT_COLLECTION_ID_MOUSE, INPUT_USAGE_AXIS_VSCROLL,
167                mRelWheel, 0);
168    }
169    if (mRelHWheel != 0) {
170        getInputReport()->setIntUsage(INPUT_COLLECTION_ID_MOUSE, INPUT_USAGE_AXIS_HSCROLL,
171                mRelHWheel, 0);
172    }
173
174    // Report and reset.
175    getInputReport()->reportEvent(getDeviceHandle());
176    mUpdatedButtonMask.clear();
177    mButtonValues.clear();
178    mRelX = 0;
179    mRelY = 0;
180    mRelWheel = 0;
181    mRelHWheel = 0;
182}
183
184}  // namespace android
185