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