gamepad_device_mac.mm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. // Copyright (c) 2018 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. #include "device/gamepad/gamepad_device_mac.h"
  5. #import <Foundation/Foundation.h>
  6. #include "base/mac/foundation_util.h"
  7. #include "base/mac/scoped_cftyperef.h"
  8. #include "base/strings/sys_string_conversions.h"
  9. #include "device/gamepad/dualshock4_controller.h"
  10. #include "device/gamepad/gamepad_data_fetcher.h"
  11. #include "device/gamepad/gamepad_id_list.h"
  12. #include "device/gamepad/hid_haptic_gamepad.h"
  13. #include "device/gamepad/hid_writer_mac.h"
  14. #include "device/gamepad/xbox_hid_controller.h"
  15. namespace device {
  16. namespace {
  17. // http://www.usb.org/developers/hidpage
  18. const uint16_t kGenericDesktopUsagePage = 0x01;
  19. const uint16_t kGameControlsUsagePage = 0x05;
  20. const uint16_t kButtonUsagePage = 0x09;
  21. const uint16_t kConsumerUsagePage = 0x0c;
  22. const uint16_t kJoystickUsageNumber = 0x04;
  23. const uint16_t kGameUsageNumber = 0x05;
  24. const uint16_t kMultiAxisUsageNumber = 0x08;
  25. const uint16_t kAxisMinimumUsageNumber = 0x30;
  26. const uint16_t kSystemMainMenuUsageNumber = 0x85;
  27. const uint16_t kPowerUsageNumber = 0x30;
  28. const uint16_t kSearchUsageNumber = 0x0221;
  29. const uint16_t kHomeUsageNumber = 0x0223;
  30. const uint16_t kBackUsageNumber = 0x0224;
  31. const uint16_t kRecordUsageNumber = 0xb2;
  32. const int kRumbleMagnitudeMax = 10000;
  33. struct SpecialUsages {
  34. const uint16_t usage_page;
  35. const uint16_t usage;
  36. } kSpecialUsages[] = {
  37. // Xbox One S pre-FW update reports Xbox button as SystemMainMenu over BT.
  38. {kGenericDesktopUsagePage, kSystemMainMenuUsageNumber},
  39. // Power is used for the Guide button on the Nvidia Shield 2015 gamepad.
  40. {kConsumerUsagePage, kPowerUsageNumber},
  41. // Search is used for the Guide button on the Nvidia Shield 2017 gamepad.
  42. {kConsumerUsagePage, kSearchUsageNumber},
  43. // Start, Back, and Guide buttons are often reported as Consumer Home or
  44. // Back.
  45. {kConsumerUsagePage, kHomeUsageNumber},
  46. {kConsumerUsagePage, kBackUsageNumber},
  47. {kConsumerUsagePage, kRecordUsageNumber},
  48. };
  49. const size_t kSpecialUsagesLen = std::size(kSpecialUsages);
  50. float NormalizeAxis(CFIndex value, CFIndex min, CFIndex max) {
  51. return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
  52. }
  53. float NormalizeUInt8Axis(uint8_t value, uint8_t min, uint8_t max) {
  54. return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
  55. }
  56. float NormalizeUInt16Axis(uint16_t value, uint16_t min, uint16_t max) {
  57. return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
  58. }
  59. float NormalizeUInt32Axis(uint32_t value, uint32_t min, uint32_t max) {
  60. return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
  61. }
  62. GamepadBusType QueryBusType(IOHIDDeviceRef device) {
  63. CFStringRef transport_cf = base::mac::CFCast<CFStringRef>(
  64. IOHIDDeviceGetProperty(device, CFSTR(kIOHIDTransportKey)));
  65. if (transport_cf) {
  66. std::string transport = base::SysCFStringRefToUTF8(transport_cf);
  67. if (transport == kIOHIDTransportUSBValue)
  68. return GAMEPAD_BUS_USB;
  69. if (transport == kIOHIDTransportBluetoothValue ||
  70. transport == kIOHIDTransportBluetoothLowEnergyValue) {
  71. return GAMEPAD_BUS_BLUETOOTH;
  72. }
  73. }
  74. return GAMEPAD_BUS_UNKNOWN;
  75. }
  76. } // namespace
  77. GamepadDeviceMac::GamepadDeviceMac(int location_id,
  78. IOHIDDeviceRef device_ref,
  79. base::StringPiece product_name,
  80. int vendor_id,
  81. int product_id)
  82. : location_id_(location_id),
  83. device_ref_(device_ref),
  84. bus_type_(QueryBusType(device_ref_)),
  85. ff_device_ref_(nullptr),
  86. ff_effect_ref_(nullptr) {
  87. auto gamepad_id =
  88. GamepadIdList::Get().GetGamepadId(product_name, vendor_id, product_id);
  89. if (Dualshock4Controller::IsDualshock4(gamepad_id)) {
  90. dualshock4_ = std::make_unique<Dualshock4Controller>(
  91. gamepad_id, bus_type_, std::make_unique<HidWriterMac>(device_ref));
  92. return;
  93. }
  94. if (XboxHidController::IsXboxHid(gamepad_id)) {
  95. xbox_hid_ = std::make_unique<XboxHidController>(
  96. std::make_unique<HidWriterMac>(device_ref));
  97. return;
  98. }
  99. if (HidHapticGamepad::IsHidHaptic(vendor_id, product_id)) {
  100. hid_haptics_ = HidHapticGamepad::Create(
  101. vendor_id, product_id, std::make_unique<HidWriterMac>(device_ref));
  102. return;
  103. }
  104. if (device_ref) {
  105. ff_device_ref_ = CreateForceFeedbackDevice(device_ref);
  106. if (ff_device_ref_) {
  107. ff_effect_ref_ = CreateForceFeedbackEffect(ff_device_ref_, &ff_effect_,
  108. &ff_custom_force_, force_data_,
  109. axes_data_, direction_data_);
  110. }
  111. }
  112. }
  113. GamepadDeviceMac::~GamepadDeviceMac() = default;
  114. void GamepadDeviceMac::DoShutdown() {
  115. if (ff_device_ref_) {
  116. if (ff_effect_ref_) {
  117. FFDeviceReleaseEffect(ff_device_ref_, ff_effect_ref_);
  118. ff_effect_ref_ = nullptr;
  119. }
  120. FFReleaseDevice(ff_device_ref_);
  121. ff_device_ref_ = nullptr;
  122. }
  123. if (dualshock4_)
  124. dualshock4_->Shutdown();
  125. dualshock4_.reset();
  126. if (xbox_hid_)
  127. xbox_hid_->Shutdown();
  128. xbox_hid_.reset();
  129. if (hid_haptics_)
  130. hid_haptics_->Shutdown();
  131. hid_haptics_.reset();
  132. }
  133. // static
  134. bool GamepadDeviceMac::CheckCollection(IOHIDElementRef element) {
  135. // Check that a parent collection of this element matches one of the usage
  136. // numbers that we are looking for.
  137. while ((element = IOHIDElementGetParent(element)) != nullptr) {
  138. uint32_t usage_page = IOHIDElementGetUsagePage(element);
  139. uint32_t usage = IOHIDElementGetUsage(element);
  140. if (usage_page == kGenericDesktopUsagePage) {
  141. if (usage == kJoystickUsageNumber || usage == kGameUsageNumber ||
  142. usage == kMultiAxisUsageNumber) {
  143. return true;
  144. }
  145. }
  146. }
  147. return false;
  148. }
  149. bool GamepadDeviceMac::AddButtonsAndAxes(Gamepad* gamepad) {
  150. bool has_buttons = AddButtons(gamepad);
  151. bool has_axes = AddAxes(gamepad);
  152. gamepad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
  153. return (has_buttons || has_axes);
  154. }
  155. bool GamepadDeviceMac::AddButtons(Gamepad* gamepad) {
  156. base::ScopedCFTypeRef<CFArrayRef> elements_cf(IOHIDDeviceCopyMatchingElements(
  157. device_ref_, nullptr, kIOHIDOptionsTypeNone));
  158. NSArray* elements = base::mac::CFToNSCast(elements_cf);
  159. DCHECK(elements);
  160. DCHECK(gamepad);
  161. memset(gamepad->buttons, 0, sizeof(gamepad->buttons));
  162. std::fill(button_elements_, button_elements_ + Gamepad::kButtonsLengthCap,
  163. nullptr);
  164. std::vector<IOHIDElementRef> special_element(kSpecialUsagesLen, nullptr);
  165. size_t button_count = 0;
  166. size_t unmapped_button_count = 0;
  167. for (id elem in elements) {
  168. IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
  169. if (!CheckCollection(element))
  170. continue;
  171. uint32_t usage_page = IOHIDElementGetUsagePage(element);
  172. uint32_t usage = IOHIDElementGetUsage(element);
  173. if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button) {
  174. if (usage_page == kButtonUsagePage && usage > 0) {
  175. size_t button_index = size_t{usage - 1};
  176. // Ignore buttons with large usage values.
  177. if (button_index >= Gamepad::kButtonsLengthCap)
  178. continue;
  179. // Button index already assigned, ignore.
  180. if (button_elements_[button_index])
  181. continue;
  182. button_elements_[button_index] = element;
  183. gamepad->buttons[button_index].used = true;
  184. button_count = std::max(button_count, button_index + 1);
  185. } else {
  186. // Check for common gamepad buttons that are not on the Button usage
  187. // page. Button indices are assigned in a second pass.
  188. for (size_t special_index = 0; special_index < kSpecialUsagesLen;
  189. ++special_index) {
  190. const auto& special = kSpecialUsages[special_index];
  191. if (usage_page == special.usage_page && usage == special.usage) {
  192. special_element[special_index] = element;
  193. ++unmapped_button_count;
  194. }
  195. }
  196. }
  197. }
  198. }
  199. if (unmapped_button_count > 0) {
  200. // Insert unmapped buttons at unused button indices.
  201. size_t button_index = 0;
  202. for (size_t special_index = 0; special_index < kSpecialUsagesLen;
  203. ++special_index) {
  204. if (!special_element[special_index])
  205. continue;
  206. // Advance to the next unused button index.
  207. while (button_index < Gamepad::kButtonsLengthCap &&
  208. button_elements_[button_index]) {
  209. ++button_index;
  210. }
  211. if (button_index >= Gamepad::kButtonsLengthCap)
  212. break;
  213. button_elements_[button_index] = special_element[special_index];
  214. gamepad->buttons[button_index].used = true;
  215. button_count = std::max(button_count, button_index + 1);
  216. if (--unmapped_button_count == 0)
  217. break;
  218. }
  219. }
  220. gamepad->buttons_length = button_count;
  221. return gamepad->buttons_length > 0;
  222. }
  223. bool GamepadDeviceMac::AddAxes(Gamepad* gamepad) {
  224. base::ScopedCFTypeRef<CFArrayRef> elements_cf(IOHIDDeviceCopyMatchingElements(
  225. device_ref_, nullptr, kIOHIDOptionsTypeNone));
  226. NSArray* elements = base::mac::CFToNSCast(elements_cf);
  227. DCHECK(elements);
  228. DCHECK(gamepad);
  229. memset(gamepad->axes, 0, sizeof(gamepad->axes));
  230. std::fill(axis_elements_, axis_elements_ + Gamepad::kAxesLengthCap, nullptr);
  231. std::fill(axis_minimums_, axis_minimums_ + Gamepad::kAxesLengthCap, 0);
  232. std::fill(axis_maximums_, axis_maximums_ + Gamepad::kAxesLengthCap, 0);
  233. std::fill(axis_report_sizes_, axis_report_sizes_ + Gamepad::kAxesLengthCap,
  234. 0);
  235. // Most axes are mapped so that their index in the Gamepad axes array
  236. // corresponds to the usage ID. However, this is not possible when the usage
  237. // ID would cause the axis index to exceed the bounds of the axes array.
  238. // Axes with large usage IDs are mapped in a second pass.
  239. size_t axis_count = 0;
  240. size_t unmapped_axis_count = 0;
  241. for (id elem in elements) {
  242. IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
  243. if (!CheckCollection(element))
  244. continue;
  245. uint32_t usage_page = IOHIDElementGetUsagePage(element);
  246. uint32_t usage = IOHIDElementGetUsage(element);
  247. if (IOHIDElementGetType(element) != kIOHIDElementTypeInput_Misc ||
  248. usage < kAxisMinimumUsageNumber) {
  249. continue;
  250. }
  251. size_t axis_index = size_t{usage - kAxisMinimumUsageNumber};
  252. if (axis_index < Gamepad::kAxesLengthCap) {
  253. // Axis index already assigned, ignore.
  254. if (axis_elements_[axis_index])
  255. continue;
  256. axis_elements_[axis_index] = element;
  257. axis_count = std::max(axis_count, axis_index + 1);
  258. } else if (usage_page <= kGameControlsUsagePage) {
  259. // Assign an index for this axis in the second pass.
  260. ++unmapped_axis_count;
  261. }
  262. }
  263. if (unmapped_axis_count > 0) {
  264. // Insert unmapped axes at unused axis indices.
  265. size_t axis_index = 0;
  266. for (id elem in elements) {
  267. IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
  268. if (!CheckCollection(element))
  269. continue;
  270. uint32_t usage_page = IOHIDElementGetUsagePage(element);
  271. uint32_t usage = IOHIDElementGetUsage(element);
  272. if (IOHIDElementGetType(element) != kIOHIDElementTypeInput_Misc ||
  273. usage < kAxisMinimumUsageNumber ||
  274. usage_page > kGameControlsUsagePage) {
  275. continue;
  276. }
  277. // Ignore axes with small usage IDs that should have been mapped in the
  278. // initial pass.
  279. if (size_t{usage - kAxisMinimumUsageNumber} < Gamepad::kAxesLengthCap)
  280. continue;
  281. // Advance to the next unused axis index.
  282. while (axis_index < Gamepad::kAxesLengthCap &&
  283. axis_elements_[axis_index]) {
  284. ++axis_index;
  285. }
  286. if (axis_index >= Gamepad::kAxesLengthCap)
  287. break;
  288. axis_elements_[axis_index] = element;
  289. axis_count = std::max(axis_count, axis_index + 1);
  290. if (--unmapped_axis_count == 0)
  291. break;
  292. }
  293. }
  294. // Fetch the logical range and report size for each axis.
  295. for (size_t axis_index = 0; axis_index < axis_count; ++axis_index) {
  296. IOHIDElementRef element = axis_elements_[axis_index];
  297. if (element != nullptr) {
  298. CFIndex axis_min = IOHIDElementGetLogicalMin(element);
  299. CFIndex axis_max = IOHIDElementGetLogicalMax(element);
  300. // Some HID axes report a logical range of -1 to 0 signed, which must be
  301. // interpreted as 0 to -1 unsigned for correct normalization behavior.
  302. if (axis_min == -1 && axis_max == 0) {
  303. axis_max = -1;
  304. axis_min = 0;
  305. }
  306. axis_minimums_[axis_index] = axis_min;
  307. axis_maximums_[axis_index] = axis_max;
  308. axis_report_sizes_[axis_index] = IOHIDElementGetReportSize(element);
  309. gamepad->axes_used |= 1 << axis_index;
  310. }
  311. }
  312. gamepad->axes_length = axis_count;
  313. return gamepad->axes_length > 0;
  314. }
  315. void GamepadDeviceMac::UpdateGamepadForValue(IOHIDValueRef value,
  316. Gamepad* gamepad) {
  317. DCHECK(gamepad);
  318. IOHIDElementRef element = IOHIDValueGetElement(value);
  319. uint32_t value_length = IOHIDValueGetLength(value);
  320. if (dualshock4_) {
  321. // Handle Dualshock4 input reports that do not specify HID gamepad usages
  322. // in the report descriptor.
  323. uint32_t report_id = IOHIDElementGetReportID(element);
  324. auto report = base::make_span(IOHIDValueGetBytePtr(value), value_length);
  325. if (dualshock4_->ProcessInputReport(report_id, report, gamepad))
  326. return;
  327. }
  328. // Values larger than 4 bytes cannot be handled by IOHIDValueGetIntegerValue.
  329. if (value_length > 4)
  330. return;
  331. // Find and fill in the associated button event, if any.
  332. for (size_t i = 0; i < gamepad->buttons_length; ++i) {
  333. if (button_elements_[i] == element) {
  334. bool pressed = IOHIDValueGetIntegerValue(value);
  335. gamepad->buttons[i].pressed = pressed;
  336. gamepad->buttons[i].value = pressed ? 1.f : 0.f;
  337. gamepad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
  338. return;
  339. }
  340. }
  341. // Find and fill in the associated axis event, if any.
  342. for (size_t i = 0; i < gamepad->axes_length; ++i) {
  343. if (axis_elements_[i] == element) {
  344. CFIndex axis_min = axis_minimums_[i];
  345. CFIndex axis_max = axis_maximums_[i];
  346. CFIndex axis_value = IOHIDValueGetIntegerValue(value);
  347. if (axis_min > axis_max) {
  348. // We'll need to interpret this axis as unsigned during normalization.
  349. switch (axis_report_sizes_[i]) {
  350. case 8:
  351. gamepad->axes[i] =
  352. NormalizeUInt8Axis(axis_value, axis_min, axis_max);
  353. break;
  354. case 16:
  355. gamepad->axes[i] =
  356. NormalizeUInt16Axis(axis_value, axis_min, axis_max);
  357. break;
  358. case 32:
  359. gamepad->axes[i] =
  360. NormalizeUInt32Axis(axis_value, axis_min, axis_max);
  361. break;
  362. }
  363. } else {
  364. gamepad->axes[i] = NormalizeAxis(axis_value, axis_min, axis_max);
  365. }
  366. gamepad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
  367. return;
  368. }
  369. }
  370. }
  371. bool GamepadDeviceMac::SupportsVibration() {
  372. return dualshock4_ || xbox_hid_ || hid_haptics_ || ff_device_ref_;
  373. }
  374. void GamepadDeviceMac::SetVibration(mojom::GamepadEffectParametersPtr params) {
  375. if (dualshock4_) {
  376. dualshock4_->SetVibration(std::move(params));
  377. return;
  378. }
  379. if (xbox_hid_) {
  380. xbox_hid_->SetVibration(std::move(params));
  381. return;
  382. }
  383. if (hid_haptics_) {
  384. hid_haptics_->SetVibration(std::move(params));
  385. return;
  386. }
  387. if (ff_device_ref_) {
  388. FFCUSTOMFORCE* ff_custom_force =
  389. static_cast<FFCUSTOMFORCE*>(ff_effect_.lpvTypeSpecificParams);
  390. DCHECK(ff_custom_force);
  391. DCHECK(ff_custom_force->rglForceData);
  392. ff_custom_force->rglForceData[0] =
  393. static_cast<LONG>(params->strong_magnitude * kRumbleMagnitudeMax);
  394. ff_custom_force->rglForceData[1] =
  395. static_cast<LONG>(params->weak_magnitude * kRumbleMagnitudeMax);
  396. // Download the effect to the device and start the effect.
  397. HRESULT res = FFEffectSetParameters(
  398. ff_effect_ref_, &ff_effect_,
  399. FFEP_DURATION | FFEP_STARTDELAY | FFEP_TYPESPECIFICPARAMS);
  400. if (res == FF_OK)
  401. FFEffectStart(ff_effect_ref_, 1, FFES_SOLO);
  402. }
  403. }
  404. void GamepadDeviceMac::SetZeroVibration() {
  405. if (dualshock4_) {
  406. dualshock4_->SetZeroVibration();
  407. return;
  408. }
  409. if (xbox_hid_) {
  410. xbox_hid_->SetZeroVibration();
  411. return;
  412. }
  413. if (hid_haptics_) {
  414. hid_haptics_->SetZeroVibration();
  415. return;
  416. }
  417. if (ff_effect_ref_)
  418. FFEffectStop(ff_effect_ref_);
  419. }
  420. // static
  421. FFDeviceObjectReference GamepadDeviceMac::CreateForceFeedbackDevice(
  422. IOHIDDeviceRef device_ref) {
  423. io_service_t service = IOHIDDeviceGetService(device_ref);
  424. if (service == MACH_PORT_NULL)
  425. return nullptr;
  426. HRESULT res = FFIsForceFeedback(service);
  427. if (res != FF_OK)
  428. return nullptr;
  429. FFDeviceObjectReference ff_device_ref;
  430. res = FFCreateDevice(service, &ff_device_ref);
  431. if (res != FF_OK)
  432. return nullptr;
  433. return ff_device_ref;
  434. }
  435. // static
  436. FFEffectObjectReference GamepadDeviceMac::CreateForceFeedbackEffect(
  437. FFDeviceObjectReference ff_device_ref,
  438. FFEFFECT* ff_effect,
  439. FFCUSTOMFORCE* ff_custom_force,
  440. LONG* force_data,
  441. DWORD* axes_data,
  442. LONG* direction_data) {
  443. DCHECK(ff_effect);
  444. DCHECK(ff_custom_force);
  445. DCHECK(force_data);
  446. DCHECK(axes_data);
  447. DCHECK(direction_data);
  448. FFCAPABILITIES caps;
  449. HRESULT res = FFDeviceGetForceFeedbackCapabilities(ff_device_ref, &caps);
  450. if (res != FF_OK)
  451. return nullptr;
  452. if ((caps.supportedEffects & FFCAP_ET_CUSTOMFORCE) == 0)
  453. return nullptr;
  454. force_data[0] = 0;
  455. force_data[1] = 0;
  456. axes_data[0] = caps.ffAxes[0];
  457. axes_data[1] = caps.ffAxes[1];
  458. direction_data[0] = 0;
  459. direction_data[1] = 0;
  460. ff_custom_force->cChannels = 2;
  461. ff_custom_force->cSamples = 2;
  462. ff_custom_force->rglForceData = force_data;
  463. ff_custom_force->dwSamplePeriod = 100000; // 100 ms
  464. ff_effect->dwSize = sizeof(FFEFFECT);
  465. ff_effect->dwFlags = FFEFF_OBJECTOFFSETS | FFEFF_SPHERICAL;
  466. ff_effect->dwDuration = 5000000; // 5 seconds
  467. ff_effect->dwSamplePeriod = 100000; // 100 ms
  468. ff_effect->dwGain = 10000;
  469. ff_effect->dwTriggerButton = FFEB_NOTRIGGER;
  470. ff_effect->dwTriggerRepeatInterval = 0;
  471. ff_effect->cAxes = caps.numFfAxes;
  472. ff_effect->rgdwAxes = axes_data;
  473. ff_effect->rglDirection = direction_data;
  474. ff_effect->lpEnvelope = nullptr;
  475. ff_effect->cbTypeSpecificParams = sizeof(FFCUSTOMFORCE);
  476. ff_effect->lpvTypeSpecificParams = ff_custom_force;
  477. ff_effect->dwStartDelay = 0;
  478. FFEffectObjectReference ff_effect_ref;
  479. res = FFDeviceCreateEffect(ff_device_ref, kFFEffectType_CustomForce_ID,
  480. ff_effect, &ff_effect_ref);
  481. if (res != FF_OK)
  482. return nullptr;
  483. return ff_effect_ref;
  484. }
  485. base::WeakPtr<AbstractHapticGamepad> GamepadDeviceMac::GetWeakPtr() {
  486. return weak_factory_.GetWeakPtr();
  487. }
  488. } // namespace device