[WTF] Move currentCPUTime and sleep(Seconds) to CPUTime.h and Seconds.h respectively
[WebKit-https.git] / Source / WebCore / platform / gamepad / 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) && PLATFORM(MAC)
30
31 #include <IOKit/hid/IOHIDElement.h>
32 #include <IOKit/hid/IOHIDUsageTables.h>
33 #include <IOKit/hid/IOHIDValue.h>
34 #include <wtf/cf/TypeCastsCF.h>
35 #include <wtf/text/CString.h>
36 #include <wtf/text/WTFString.h>
37
38 WTF_DECLARE_CF_TYPE_TRAIT(IOHIDElement);
39
40 namespace WebCore {
41
42 HIDGamepad::HIDGamepad(IOHIDDeviceRef hidDevice, unsigned index)
43     : PlatformGamepad(index)
44     , m_hidDevice(hidDevice)
45 {
46     m_connectTime = m_lastUpdateTime = MonotonicTime::now();
47
48     CFNumberRef cfVendorID = (CFNumberRef)IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
49     CFNumberRef cfProductID = (CFNumberRef)IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
50
51     int vendorID, productID;
52     CFNumberGetValue(cfVendorID, kCFNumberIntType, &vendorID);
53     CFNumberGetValue(cfProductID, kCFNumberIntType, &productID);
54
55     CFStringRef cfProductName = (CFStringRef)IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
56     String productName(cfProductName);
57
58     // Currently the spec has no formatting for the id string.
59     // This string formatting matches Firefox.
60     m_id = String::format("%x-%x-%s", vendorID, productID, productName.utf8().data());
61
62     initElements();
63 }
64
65 void HIDGamepad::getCurrentValueForElement(const HIDGamepadElement& gamepadElement)
66 {
67     IOHIDElementRef element = gamepadElement.iohidElement.get();
68     IOHIDValueRef value;
69     if (IOHIDDeviceGetValue(IOHIDElementGetDevice(element), element, &value) == kIOReturnSuccess)
70         valueChanged(value);
71 }
72
73 void HIDGamepad::initElements()
74 {
75     RetainPtr<CFArrayRef> elements = adoptCF(IOHIDDeviceCopyMatchingElements(m_hidDevice.get(), NULL, kIOHIDOptionsTypeNone));
76     initElementsFromArray(elements.get());
77
78     // Buttons are specified to appear highest priority first in the array.
79     std::sort(m_buttons.begin(), m_buttons.end(), [](auto& a, auto& b) {
80         return a->priority < b->priority;
81     });
82
83     m_axisValues.resize(m_axes.size());
84     m_buttonValues.resize(m_buttons.size() + (m_dPads.size() * 4));
85
86     for (auto& button : m_buttons)
87         getCurrentValueForElement(button.get());
88
89     for (auto& dPad : m_dPads)
90         getCurrentValueForElement(dPad.get());
91
92     for (auto& axis : m_axes)
93         getCurrentValueForElement(axis.get());
94 }
95
96 void HIDGamepad::initElementsFromArray(CFArrayRef elements)
97 {
98     for (CFIndex i = 0, count = CFArrayGetCount(elements); i < count; ++i) {
99         IOHIDElementRef element = checked_cf_cast<IOHIDElementRef>(CFArrayGetValueAtIndex(elements, i));
100         if (CFGetTypeID(element) != IOHIDElementGetTypeID())
101             continue;
102
103         // As a physical element can appear in the device twice (in different collections) and can be
104         // represented by different IOHIDElementRef objects, we look at the IOHIDElementCookie which
105         // is meant to be unique for each physical element.
106         IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
107         if (m_elementMap.contains(cookie))
108             continue;
109
110         IOHIDElementType type = IOHIDElementGetType(element);
111
112         if ((type == kIOHIDElementTypeInput_Misc || type == kIOHIDElementTypeInput_Button)) {
113             if (maybeAddButton(element))
114                 continue;
115             if (maybeAddDPad(element))
116                 continue;
117         }
118
119         if ((type == kIOHIDElementTypeInput_Misc || type == kIOHIDElementTypeInput_Axis) && maybeAddAxis(element))
120             continue;
121
122         if (type == kIOHIDElementTypeCollection)
123             initElementsFromArray(IOHIDElementGetChildren(element));
124     }
125 }
126
127 bool HIDGamepad::maybeAddButton(IOHIDElementRef element)
128 {
129     uint32_t usagePage = IOHIDElementGetUsagePage(element);
130     if (usagePage != kHIDPage_Button && usagePage != kHIDPage_GenericDesktop)
131         return false;
132
133     uint32_t usage = IOHIDElementGetUsage(element);
134
135     if (usagePage == kHIDPage_GenericDesktop) {
136         if (usage < kHIDUsage_GD_DPadUp || usage > kHIDUsage_GD_DPadLeft)
137             return false;
138         usage = std::numeric_limits<uint32_t>::max();
139     } else if (!usage)
140         return false;
141
142     CFIndex min = IOHIDElementGetLogicalMin(element);
143     CFIndex max = IOHIDElementGetLogicalMax(element);
144
145     m_buttons.append(makeUniqueRef<HIDGamepadButton>(usage, min, max, element));
146
147     IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
148     m_elementMap.set(cookie, &m_buttons.last().get());
149
150     return true;
151 }
152
153 bool HIDGamepad::maybeAddDPad(IOHIDElementRef element)
154 {
155     uint32_t usagePage = IOHIDElementGetUsagePage(element);
156     if (usagePage != kHIDPage_GenericDesktop)
157         return false;
158
159     uint32_t usage = IOHIDElementGetUsage(element);
160     if (!usage || usage != kHIDUsage_GD_Hatswitch)
161         return false;
162
163     CFIndex min = IOHIDElementGetLogicalMin(element);
164     CFIndex max = IOHIDElementGetLogicalMax(element);
165
166     m_dPads.append(makeUniqueRef<HIDGamepadDPad>(min, max, element));
167
168     IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
169     m_elementMap.set(cookie, &m_dPads.last().get());
170
171     return true;
172 }
173
174 bool HIDGamepad::maybeAddAxis(IOHIDElementRef element)
175 {
176     uint32_t usagePage = IOHIDElementGetUsagePage(element);
177     if (usagePage != kHIDPage_GenericDesktop)
178         return false;
179
180     uint32_t usage = IOHIDElementGetUsage(element);
181     // This range covers the standard axis usages.
182     if (usage < kHIDUsage_GD_X || usage > kHIDUsage_GD_Rz)
183         return false;
184
185     CFIndex min = IOHIDElementGetPhysicalMin(element);
186     CFIndex max = IOHIDElementGetPhysicalMax(element);
187
188     m_axes.append(makeUniqueRef<HIDGamepadAxis>(min, max, element));
189
190     IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
191     m_elementMap.set(cookie, &m_axes.last().get());
192
193     return true;
194 }
195
196 static void fillInButtonValues(int value, double& button0, double& button1, double& button2, double& button3)
197 {
198     // Standard DPads have 8 possible position values, moving around a circle:
199     // 0 - Up
200     // 1 - Up + right
201     // 2 - Right
202     // 3 - Down + right
203     // 4 - Down
204     // 5 - Down + left
205     // 6 - Left
206     // 7 - Up and Left
207     // These 8 positions are mapped on to 4 button positions:
208     // 0 - Up
209     // 1 - Down
210     // 2 - Left
211     // 3 - Right
212
213     if (value < 0 || value > 7) {
214         // Handle an invalid input value that we don't know how to interpret.
215         button0 = 0.0;
216         button1 = 0.0;
217         button2 = 0.0;
218         button3 = 0.0;
219         return;
220     }
221
222     button0 = value < 2 || value == 7 ? 1.0 : 0.0;
223     button1 = value > 2 && value < 6 ? 1.0 : 0.0;
224     button2 = value > 4 ? 1.0 : 0.0;
225     button3 = value > 0 && value < 4 ? 1.0 : 0.0;
226 }
227
228 HIDInputType HIDGamepad::valueChanged(IOHIDValueRef value)
229 {
230     IOHIDElementCookie cookie = IOHIDElementGetCookie(IOHIDValueGetElement(value));
231     HIDGamepadElement* element = m_elementMap.get(cookie);
232
233     // This might be an element we don't currently handle as input so we can skip it.
234     if (!element)
235         return HIDInputType::NotAButtonPress;
236
237     element->rawValue = IOHIDValueGetScaledValue(value, kIOHIDValueScaleTypePhysical);
238
239     if (element->isButton()) {
240         for (unsigned i = 0; i < m_buttons.size(); ++i) {
241             if (&m_buttons[i].get() == element) {
242                 m_buttonValues[i] = element->normalizedValue();
243                 break;
244             }
245         }
246     } else if (element->isAxis()) {
247         for (unsigned i = 0; i < m_axes.size(); ++i) {
248             if (&m_axes[i].get() == element) {
249                 m_axisValues[i] = element->normalizedValue();
250                 break;
251             }
252         }
253     } else if (element->isDPad()) {
254         int intValue = IOHIDValueGetIntegerValue(value) - element->min;
255         for (unsigned i = 0; i < m_dPads.size(); ++i) {
256             if (&m_dPads[i].get() != element)
257                 continue;
258
259             // Each DPad represents 4 button values which are tacked on to the end of the values from non-DPad buttons.
260             unsigned firstButtonValue = m_buttons.size() + i * 4;
261
262             ASSERT(m_buttonValues.size() > firstButtonValue + 3);
263
264             fillInButtonValues(intValue, m_buttonValues[firstButtonValue], m_buttonValues[firstButtonValue + 1], m_buttonValues[firstButtonValue + 2], m_buttonValues[firstButtonValue + 3]);
265         }
266     } else
267         ASSERT_NOT_REACHED();
268
269     m_lastUpdateTime = MonotonicTime::now();
270
271     return element->isButton() ? HIDInputType::ButtonPress : HIDInputType::NotAButtonPress;
272 }
273
274 } // namespace WebCore
275
276 #endif // ENABLE(GAMEPAD) && PLATFORM(MAC)