a1049cbfa6e438fd59f5124a73d6d35131b12a2b
[WebKit-https.git] / Source / WebCore / platform / mac / HIDGamepad.cpp
1 /*
2  * Copyright (C) 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "HIDGamepad.h"
28
29 #if ENABLE(GAMEPAD)
30
31 #include <IOKit/hid/IOHIDElement.h>
32 #include <IOKit/hid/IOHIDUsageTables.h>
33 #include <IOKit/hid/IOHIDValue.h>
34 #include <wtf/CurrentTime.h>
35 #include <wtf/text/CString.h>
36 #include <wtf/text/WTFString.h>
37
38 namespace WebCore {
39
40 HIDGamepad::HIDGamepad(IOHIDDeviceRef hidDevice)
41     : m_hidDevice(hidDevice)
42 {
43     m_connectTime = m_lastUpdateTime = monotonicallyIncreasingTime();
44
45     CFNumberRef cfVendorID = (CFNumberRef)IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
46     CFNumberRef cfProductID = (CFNumberRef)IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
47
48     int vendorID, productID;
49     CFNumberGetValue(cfVendorID, kCFNumberIntType, &vendorID);
50     CFNumberGetValue(cfProductID, kCFNumberIntType, &productID);
51
52     CFStringRef cfProductName = (CFStringRef)IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
53     String productName(cfProductName);
54
55     // Currently the spec has no formatting for the id string.
56     // This string formatting matches Firefox.
57     m_id = String::format("%x-%x-%s", vendorID, productID, productName.utf8().data());
58
59     initElements();
60 }
61
62 void HIDGamepad::initElements()
63 {
64     RetainPtr<CFArrayRef> elements = adoptCF(IOHIDDeviceCopyMatchingElements(m_hidDevice.get(), NULL, kIOHIDOptionsTypeNone));
65     initElementsFromArray(elements.get());
66
67     // Buttons are specified to appear highest priority first in the array.
68     std::sort(m_buttons.begin(), m_buttons.end(), [](const std::unique_ptr<HIDGamepadButton>& a, const std::unique_ptr<HIDGamepadButton>& b) {
69         return a->priority < b->priority;
70     });
71
72     m_axisValues.resize(m_axes.size());
73     m_buttonValues.resize(m_buttons.size());
74 }
75
76 void HIDGamepad::initElementsFromArray(CFArrayRef elements)
77 {
78     for (int i = 0; i < CFArrayGetCount(elements); ++i) {
79         IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
80         if (CFGetTypeID(element) != IOHIDElementGetTypeID())
81             continue;
82
83         // As a physical element can appear in the device twice (in different collections) and can be
84         // represented by different IOHIDElementRef objects, we look at the IOHIDElementCookie which
85         // is meant to be unique for each physical element.
86         IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
87         if (m_elementMap.contains(cookie))
88             continue;
89
90         IOHIDElementType type = IOHIDElementGetType(element);
91
92         if ((type == kIOHIDElementTypeInput_Misc || type == kIOHIDElementTypeInput_Button) && maybeAddButton(element))
93             continue;
94
95         if ((type == kIOHIDElementTypeInput_Misc || type == kIOHIDElementTypeInput_Axis) && maybeAddAxis(element))
96             continue;
97
98         if (type == kIOHIDElementTypeCollection)
99             initElementsFromArray(IOHIDElementGetChildren(element));
100     }
101 }
102
103 bool HIDGamepad::maybeAddButton(IOHIDElementRef element)
104 {
105     uint32_t usagePage = IOHIDElementGetUsagePage(element);
106     if (usagePage != kHIDPage_Button)
107         return false;
108
109     uint32_t usage = IOHIDElementGetUsage(element);
110     if (!usage)
111         return false;
112
113     CFIndex min = IOHIDElementGetLogicalMin(element);
114     CFIndex max = IOHIDElementGetLogicalMax(element);
115
116     m_buttons.append(std::make_unique<HIDGamepadButton>(usage, min, max));
117
118     IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
119     m_elementMap.set(cookie, m_buttons.last().get());
120
121     return true;
122 }
123
124 bool HIDGamepad::maybeAddAxis(IOHIDElementRef element)
125 {
126     uint32_t usagePage = IOHIDElementGetUsagePage(element);
127     if (usagePage != kHIDPage_GenericDesktop)
128         return false;
129
130     uint32_t usage = IOHIDElementGetUsage(element);
131     // This range covers the standard axis usages.
132     if (usage < kHIDUsage_GD_X || usage > kHIDUsage_GD_Rz)
133         return false;
134
135     CFIndex min = IOHIDElementGetPhysicalMin(element);
136     CFIndex max = IOHIDElementGetPhysicalMax(element);
137
138     m_axes.append(std::make_unique<HIDGamepadAxis>(min, max));
139
140     IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
141     m_elementMap.set(cookie, m_axes.last().get());
142
143     return true;
144 }
145
146 void HIDGamepad::valueChanged(IOHIDValueRef value)
147 {
148     IOHIDElementCookie cookie = IOHIDElementGetCookie(IOHIDValueGetElement(value));
149     HIDGamepadElement* element = m_elementMap.get(cookie);
150
151     // This might be an element we don't currently handle as input so we can skip it.
152     if (!element)
153         return;
154
155     element->rawValue = IOHIDValueGetScaledValue(value, kIOHIDValueScaleTypePhysical);
156
157     if (element->isButton()) {
158         for (unsigned i = 0; i < m_buttons.size(); ++i) {
159             if (m_buttons[i].get() == element)
160                 m_buttonValues[i] = element->normalizedValue();
161         }
162     } else if (element->isAxis()) {
163         for (unsigned i = 0; i < m_axes.size(); ++i) {
164             if (m_axes[i].get() == element)
165                 m_axisValues[i] = element->normalizedValue();
166         }
167     } else
168         ASSERT_NOT_REACHED();
169
170     m_lastUpdateTime = monotonicallyIncreasingTime();
171 }
172
173 } // namespace WebCore
174
175 #endif // ENABLE(GAMEPAD)