1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "device/gamepad/gamepad_platform_data_fetcher_mac.h"
6
7#include <stdint.h>
8#include <string.h>
9
10#include "base/mac/foundation_util.h"
11#include "base/mac/scoped_nsobject.h"
12#include "base/sequenced_task_runner.h"
13#include "base/strings/sys_string_conversions.h"
14#include "base/time/time.h"
15#include "device/gamepad/gamepad_blocklist.h"
16#include "device/gamepad/gamepad_device_mac.h"
17#include "device/gamepad/gamepad_id_list.h"
18#include "device/gamepad/gamepad_uma.h"
19#include "device/gamepad/nintendo_controller.h"
20
21#import <Foundation/Foundation.h>
22#include <IOKit/hid/IOHIDKeys.h>
23
24namespace device {
25
26namespace {
27
28// http://www.usb.org/developers/hidpage
29const uint16_t kGenericDesktopUsagePage = 0x01;
30const uint16_t kJoystickUsageNumber = 0x04;
31const uint16_t kGameUsageNumber = 0x05;
32const uint16_t kMultiAxisUsageNumber = 0x08;
33
34NSDictionary* DeviceMatching(uint32_t usage_page, uint32_t usage) {
35  return [NSDictionary
36      dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:usage_page],
37                                   base::mac::CFToNSCast(
38                                       CFSTR(kIOHIDDeviceUsagePageKey)),
39                                   [NSNumber numberWithUnsignedInt:usage],
40                                   base::mac::CFToNSCast(
41                                       CFSTR(kIOHIDDeviceUsageKey)),
42                                   nil];
43}
44
45}  // namespace
46
47GamepadPlatformDataFetcherMac::GamepadPlatformDataFetcherMac() = default;
48
49GamepadSource GamepadPlatformDataFetcherMac::source() {
50  return Factory::static_source();
51}
52
53void GamepadPlatformDataFetcherMac::OnAddedToProvider() {
54  hid_manager_ref_.reset(
55      IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone));
56  if (CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) {
57    enabled_ = false;
58    return;
59  }
60
61  base::scoped_nsobject<NSArray> criteria(
62      [[NSArray alloc] initWithObjects:DeviceMatching(kGenericDesktopUsagePage,
63                                                      kJoystickUsageNumber),
64                                       DeviceMatching(kGenericDesktopUsagePage,
65                                                      kGameUsageNumber),
66                                       DeviceMatching(kGenericDesktopUsagePage,
67                                                      kMultiAxisUsageNumber),
68                                       nil]);
69  IOHIDManagerSetDeviceMatchingMultiple(hid_manager_ref_,
70                                        base::mac::NSToCFCast(criteria));
71
72  RegisterForNotifications();
73}
74
75void GamepadPlatformDataFetcherMac::RegisterForNotifications() {
76  // Register for plug/unplug notifications.
77  IOHIDManagerRegisterDeviceMatchingCallback(hid_manager_ref_,
78                                             DeviceAddCallback, this);
79  IOHIDManagerRegisterDeviceRemovalCallback(hid_manager_ref_,
80                                            DeviceRemoveCallback, this);
81
82  // Register for value change notifications.
83  IOHIDManagerRegisterInputValueCallback(hid_manager_ref_, ValueChangedCallback,
84                                         this);
85
86  IOHIDManagerScheduleWithRunLoop(hid_manager_ref_, CFRunLoopGetCurrent(),
87                                  kCFRunLoopDefaultMode);
88
89  enabled_ = IOHIDManagerOpen(hid_manager_ref_, kIOHIDOptionsTypeNone) ==
90             kIOReturnSuccess;
91}
92
93void GamepadPlatformDataFetcherMac::UnregisterFromNotifications() {
94  IOHIDManagerUnscheduleFromRunLoop(hid_manager_ref_, CFRunLoopGetCurrent(),
95                                    kCFRunLoopDefaultMode);
96  IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone);
97}
98
99void GamepadPlatformDataFetcherMac::PauseHint(bool pause) {
100  paused_ = pause;
101}
102
103GamepadPlatformDataFetcherMac::~GamepadPlatformDataFetcherMac() {
104  UnregisterFromNotifications();
105  for (auto& iter : devices_) {
106    iter.second->Shutdown();
107  }
108}
109
110GamepadPlatformDataFetcherMac*
111GamepadPlatformDataFetcherMac::InstanceFromContext(void* context) {
112  return reinterpret_cast<GamepadPlatformDataFetcherMac*>(context);
113}
114
115void GamepadPlatformDataFetcherMac::DeviceAddCallback(void* context,
116                                                      IOReturn result,
117                                                      void* sender,
118                                                      IOHIDDeviceRef ref) {
119  InstanceFromContext(context)->DeviceAdd(ref);
120}
121
122void GamepadPlatformDataFetcherMac::DeviceRemoveCallback(void* context,
123                                                         IOReturn result,
124                                                         void* sender,
125                                                         IOHIDDeviceRef ref) {
126  InstanceFromContext(context)->DeviceRemove(ref);
127}
128
129void GamepadPlatformDataFetcherMac::ValueChangedCallback(void* context,
130                                                         IOReturn result,
131                                                         void* sender,
132                                                         IOHIDValueRef ref) {
133  InstanceFromContext(context)->ValueChanged(ref);
134}
135
136GamepadDeviceMac* GamepadPlatformDataFetcherMac::GetGamepadFromHidDevice(
137    IOHIDDeviceRef device) {
138  for (auto& iter : devices_) {
139    if (iter.second->IsSameDevice(device)) {
140      return iter.second.get();
141    }
142  }
143
144  return nullptr;
145}
146
147void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) {
148  using base::mac::CFToNSCast;
149  using base::mac::CFCastStrict;
150
151  if (!enabled_)
152    return;
153
154  NSNumber* location_id = CFToNSCast(CFCastStrict<CFNumberRef>(
155      IOHIDDeviceGetProperty(device, CFSTR(kIOHIDLocationIDKey))));
156  int location_int = [location_id intValue];
157
158  NSNumber* vendor_id = CFToNSCast(CFCastStrict<CFNumberRef>(
159      IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))));
160  NSNumber* product_id = CFToNSCast(CFCastStrict<CFNumberRef>(
161      IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey))));
162  NSNumber* version_number = CFToNSCast(CFCastStrict<CFNumberRef>(
163      IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVersionNumberKey))));
164  NSString* product = CFToNSCast(CFCastStrict<CFStringRef>(
165      IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))));
166  uint16_t vendor_int = [vendor_id intValue];
167  uint16_t product_int = [product_id intValue];
168  uint16_t version_int = [version_number intValue];
169  std::string product_name = base::SysNSStringToUTF8(product);
170
171  // Filter out devices that have gamepad-like HID usages but aren't gamepads.
172  if (GamepadIsExcluded(vendor_int, product_int))
173    return;
174
175  const auto& gamepad_id_list = GamepadIdList::Get();
176  DCHECK_EQ(kXInputTypeNone,
177            gamepad_id_list.GetXInputType(vendor_int, product_int));
178
179  if (devices_.find(location_int) != devices_.end())
180    return;
181
182  const GamepadId gamepad_id =
183      gamepad_id_list.GetGamepadId(product_name, vendor_int, product_int);
184
185  // Nintendo devices are handled by the Nintendo data fetcher.
186  if (NintendoController::IsNintendoController(gamepad_id))
187    return;
188
189  // Record the device before excluding Made for iOS gamepads. This allows us to
190  // recognize these devices even though the GameController API masks the vendor
191  // and product IDs. XInput devices are recorded elsewhere.
192  RecordConnectedGamepad(gamepad_id);
193
194  // The SteelSeries Nimbus and other Made for iOS gamepads should be handled
195  // through the GameController interface.
196  if (gamepad_id == GamepadId::kSteelSeriesProduct1420) {
197    return;
198  }
199
200  bool is_recognized = gamepad_id != GamepadId::kUnknownGamepad;
201
202  PadState* state = GetPadState(location_int, is_recognized);
203  if (!state)
204    return;  // No available slot for this device
205
206  state->mapper = GetGamepadStandardMappingFunction(
207      product_name, vendor_int, product_int, /*hid_specification_version=*/0,
208      version_int, GAMEPAD_BUS_UNKNOWN);
209
210  UpdateGamepadStrings(product_name, vendor_int, product_int,
211                       state->mapper != nullptr, state->data);
212
213  auto new_device = std::make_unique<GamepadDeviceMac>(
214      location_int, device, product_name, vendor_int, product_int);
215  if (!new_device->AddButtonsAndAxes(&state->data)) {
216    new_device->Shutdown();
217    return;
218  }
219
220  state->data.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble;
221  state->data.vibration_actuator.not_null = new_device->SupportsVibration();
222
223  state->data.connected = true;
224
225  devices_.emplace(location_int, std::move(new_device));
226}
227
228bool GamepadPlatformDataFetcherMac::DisconnectUnrecognizedGamepad(
229    int source_id) {
230  auto gamepad_iter = devices_.find(source_id);
231  if (gamepad_iter == devices_.end())
232    return false;
233  gamepad_iter->second->Shutdown();
234  devices_.erase(gamepad_iter);
235  return true;
236}
237
238void GamepadPlatformDataFetcherMac::DeviceRemove(IOHIDDeviceRef device) {
239  if (!enabled_)
240    return;
241
242  GamepadDeviceMac* gamepad_device = GetGamepadFromHidDevice(device);
243
244  if (!gamepad_device)
245    return;
246
247  gamepad_device->Shutdown();
248  devices_.erase(gamepad_device->GetLocationId());
249}
250
251void GamepadPlatformDataFetcherMac::ValueChanged(IOHIDValueRef value) {
252  if (!enabled_ || paused_)
253    return;
254
255  IOHIDElementRef element = IOHIDValueGetElement(value);
256  IOHIDDeviceRef device = IOHIDElementGetDevice(element);
257
258  GamepadDeviceMac* gamepad_device = GetGamepadFromHidDevice(device);
259
260  if (!gamepad_device)
261    return;
262
263  PadState* state = GetPadState(gamepad_device->GetLocationId());
264  if (!state)
265    return;
266
267  gamepad_device->UpdateGamepadForValue(value, &state->data);
268}
269
270void GamepadPlatformDataFetcherMac::GetGamepadData(bool) {
271  if (!enabled_)
272    return;
273
274  // Loop through and GetPadState to indicate the devices are still connected.
275  for (const auto& iter : devices_) {
276    GetPadState(iter.first);
277  }
278}
279
280void GamepadPlatformDataFetcherMac::PlayEffect(
281    int source_id,
282    mojom::GamepadHapticEffectType type,
283    mojom::GamepadEffectParametersPtr params,
284    mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback,
285    scoped_refptr<base::SequencedTaskRunner> callback_runner) {
286  auto device_iter = devices_.find(source_id);
287  if (device_iter == devices_.end()) {
288    // No connected gamepad with this location. Probably the effect was issued
289    // while the gamepad was still connected, so handle this as if it were
290    // preempted by a disconnect.
291    RunVibrationCallback(
292        std::move(callback), std::move(callback_runner),
293        mojom::GamepadHapticsResult::GamepadHapticsResultPreempted);
294    return;
295  }
296  device_iter->second->PlayEffect(type, std::move(params), std::move(callback),
297                                  std::move(callback_runner));
298}
299
300void GamepadPlatformDataFetcherMac::ResetVibration(
301    int source_id,
302    mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback,
303    scoped_refptr<base::SequencedTaskRunner> callback_runner) {
304  auto device_iter = devices_.find(source_id);
305  if (device_iter == devices_.end()) {
306    // No connected gamepad with this location. Since the gamepad is already
307    // disconnected, allow the reset to report success.
308    RunVibrationCallback(
309        std::move(callback), std::move(callback_runner),
310        mojom::GamepadHapticsResult::GamepadHapticsResultComplete);
311    return;
312  }
313  device_iter->second->ResetVibration(std::move(callback),
314                                      std::move(callback_runner));
315}
316
317}  // namespace device
318