Add WTF::move()
[WebKit-https.git] / Source / WebCore / platform / mac / HIDGamepadProvider.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 "HIDGamepadProvider.h"
28
29 #if ENABLE(GAMEPAD)
30
31 #include "GamepadProviderClient.h"
32 #include "Logging.h"
33 #include "PlatformGamepad.h"
34
35 namespace WebCore {
36
37 static const double ConnectionDelayInterval = 0.5;
38 static const double InputNotificationDelay = 0.05;
39
40 static RetainPtr<CFDictionaryRef> deviceMatchingDictionary(uint32_t usagePage, uint32_t usage)
41 {
42     ASSERT(usagePage);
43     ASSERT(usage);
44
45     RetainPtr<CFNumberRef> pageNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage));
46     RetainPtr<CFNumberRef> usageNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage));
47
48     CFStringRef keys[] = { CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) };
49     CFNumberRef values[] = { pageNumber.get(), usageNumber.get() };
50
51     return adoptCF(CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
52 }
53
54 static void deviceAddedCallback(void* context, IOReturn, void*, IOHIDDeviceRef device)
55 {
56     HIDGamepadProvider* listener = static_cast<HIDGamepadProvider*>(context);
57     listener->deviceAdded(device);
58 }
59
60 static void deviceRemovedCallback(void* context, IOReturn, void*, IOHIDDeviceRef device)
61 {
62     HIDGamepadProvider* listener = static_cast<HIDGamepadProvider*>(context);
63     listener->deviceRemoved(device);
64 }
65
66 static void deviceValuesChangedCallback(void* context, IOReturn result, void*, IOHIDValueRef value)
67 {
68     // A non-zero result value indicates an error that we can do nothing about for input values.
69     if (result)
70         return;
71
72     HIDGamepadProvider* listener = static_cast<HIDGamepadProvider*>(context);
73     listener->valuesChanged(value);
74 }
75
76 HIDGamepadProvider& HIDGamepadProvider::shared()
77 {
78     static NeverDestroyed<HIDGamepadProvider> sharedListener;
79     return sharedListener;
80 }
81
82 HIDGamepadProvider::HIDGamepadProvider()
83     : m_shouldDispatchCallbacks(false)
84     , m_connectionDelayTimer(this, &HIDGamepadProvider::connectionDelayTimerFired)
85     , m_inputNotificationTimer(this, &HIDGamepadProvider::inputNotificationTimerFired)
86 {
87     m_manager = adoptCF(IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone));
88
89     RetainPtr<CFDictionaryRef> joystickDictionary = deviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
90     RetainPtr<CFDictionaryRef> gamepadDictionary = deviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
91
92     CFDictionaryRef devices[] = { joystickDictionary.get(), gamepadDictionary.get() };
93
94     RetainPtr<CFArrayRef> matchingArray = adoptCF(CFArrayCreate(kCFAllocatorDefault, (const void**)devices, 2, &kCFTypeArrayCallBacks));
95
96     IOHIDManagerSetDeviceMatchingMultiple(m_manager.get(), matchingArray.get());
97     IOHIDManagerRegisterDeviceMatchingCallback(m_manager.get(), deviceAddedCallback, this);
98     IOHIDManagerRegisterDeviceRemovalCallback(m_manager.get(), deviceRemovedCallback, this);
99     IOHIDManagerRegisterInputValueCallback(m_manager.get(), deviceValuesChangedCallback, this);
100 }
101
102 unsigned HIDGamepadProvider::indexForNewlyConnectedDevice()
103 {
104     unsigned index = 0;
105     while (index < m_gamepadVector.size() && m_gamepadVector[index])
106         ++index;
107
108     return index;
109 }
110
111 void HIDGamepadProvider::connectionDelayTimerFired(Timer<HIDGamepadProvider>&)
112 {
113     m_shouldDispatchCallbacks = true;
114 }
115
116 void HIDGamepadProvider::openAndScheduleManager()
117 {
118     LOG(Gamepad, "HIDGamepadProvider opening/scheduling HID manager");
119
120     ASSERT(m_gamepadVector.isEmpty());
121     ASSERT(m_gamepadMap.isEmpty());
122
123     m_shouldDispatchCallbacks = false;
124
125     IOHIDManagerScheduleWithRunLoop(m_manager.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
126     IOHIDManagerOpen(m_manager.get(), kIOHIDOptionsTypeNone);
127
128     // Any connections we are notified of within the ConnectionDelayInterval of listening likely represent
129     // devices that were already connected, so we suppress notifying clients of these.
130     m_connectionDelayTimer.startOneShot(ConnectionDelayInterval);
131 }
132
133 void HIDGamepadProvider::closeAndUnscheduleManager()
134 {
135     LOG(Gamepad, "HIDGamepadProvider closing/unscheduling HID manager");
136
137     IOHIDManagerUnscheduleFromRunLoop(m_manager.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
138     IOHIDManagerClose(m_manager.get(), kIOHIDOptionsTypeNone);
139
140     m_gamepadVector.clear();
141     m_gamepadMap.clear();
142
143     m_connectionDelayTimer.stop();
144 }
145
146 void HIDGamepadProvider::startMonitoringGamepads(GamepadProviderClient* client)
147 {
148     bool shouldOpenAndScheduleManager = m_clients.isEmpty();
149
150     ASSERT(!m_clients.contains(client));
151     m_clients.add(client);
152
153     if (shouldOpenAndScheduleManager)
154         openAndScheduleManager();
155 }
156 void HIDGamepadProvider::stopMonitoringGamepads(GamepadProviderClient* client)
157 {
158     ASSERT(m_clients.contains(client));
159
160     bool shouldCloseAndUnscheduleManager = m_clients.remove(client) && m_clients.isEmpty();
161
162     if (shouldCloseAndUnscheduleManager)
163         closeAndUnscheduleManager();
164 }
165
166 void HIDGamepadProvider::deviceAdded(IOHIDDeviceRef device)
167 {
168     ASSERT(!m_gamepadMap.get(device));
169
170     LOG(Gamepad, "HIDGamepadProvider device %p added", device);
171
172     std::unique_ptr<HIDGamepad> gamepad = std::make_unique<HIDGamepad>(device);
173     unsigned index = indexForNewlyConnectedDevice();
174
175     if (m_gamepadVector.size() <= index)
176         m_gamepadVector.resize(index + 1);
177
178     m_gamepadVector[index] = gamepad.get();
179     m_gamepadMap.set(device, WTF::move(gamepad));
180
181     if (!m_shouldDispatchCallbacks) {
182         // This added device is the result of us starting to monitor gamepads.
183         // We'll get notified of all connected devices during this current spin of the runloop
184         // and we don't want to tell the client about any of them.
185         // The m_connectionDelayTimer fires in a subsequent spin of the runloop after which
186         // any connection events are actual new devices.
187         m_connectionDelayTimer.startOneShot(0);
188
189         LOG(Gamepad, "Device %p was added while suppressing callbacks, so this should be an 'already connected' event", device);
190
191         return;
192     }
193
194     for (auto& client : m_clients)
195         client->platformGamepadConnected(index);
196 }
197
198 void HIDGamepadProvider::deviceRemoved(IOHIDDeviceRef device)
199 {
200     LOG(Gamepad, "HIDGamepadProvider device %p removed", device);
201
202     std::pair<std::unique_ptr<HIDGamepad>, unsigned> removedGamepad = removeGamepadForDevice(device);
203     ASSERT(removedGamepad.first);
204
205     // Any time we get a device removed callback we know it's a real event and not an 'already connected' event.
206     // We should always stop supressing callbacks when we receive such an event.
207     m_shouldDispatchCallbacks = true;
208
209     for (auto& client : m_clients)
210         client->platformGamepadDisconnected(removedGamepad.second);
211 }
212
213 void HIDGamepadProvider::valuesChanged(IOHIDValueRef value)
214 {
215     IOHIDDeviceRef device = IOHIDElementGetDevice(IOHIDValueGetElement(value));
216
217     HIDGamepad* gamepad = m_gamepadMap.get(device);
218
219     // When starting monitoring we might get a value changed callback before we even know the device is connected.
220     if (!gamepad)
221         return;
222
223     gamepad->valueChanged(value);
224
225     // This isActive check is necessary as we want to delay input notifications from the time of the first input,
226     // and not push the notification out on every subsequent input.
227     if (!m_inputNotificationTimer.isActive())
228         m_inputNotificationTimer.startOneShot(InputNotificationDelay);
229 }
230
231 void HIDGamepadProvider::inputNotificationTimerFired(Timer<HIDGamepadProvider>&)
232 {
233     if (!m_shouldDispatchCallbacks)
234         return;
235
236     for (auto& client : m_clients)
237         client->platformGamepadInputActivity();
238 }
239
240 std::pair<std::unique_ptr<HIDGamepad>, unsigned>  HIDGamepadProvider::removeGamepadForDevice(IOHIDDeviceRef device)
241 {
242     std::pair<std::unique_ptr<HIDGamepad>, unsigned> result;
243     result.first = m_gamepadMap.take(device);
244     ASSERT(result.first);
245
246     for (unsigned i = 0; i < m_gamepadVector.size(); ++i) {
247         if (m_gamepadVector[i] == result.first.get()) {
248             result.second = i;
249             m_gamepadVector[i] = nullptr;
250             break;
251         }
252     }
253
254     return result;
255 }
256
257 } // namespace WebCore
258
259 #endif // ENABLE(GAMEPAD)