123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- // Copyright (c) 2018 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "device/gamepad/gamepad_device_mac.h"
- #import <Foundation/Foundation.h>
- #include "base/mac/foundation_util.h"
- #include "base/mac/scoped_cftyperef.h"
- #include "base/strings/sys_string_conversions.h"
- #include "device/gamepad/dualshock4_controller.h"
- #include "device/gamepad/gamepad_data_fetcher.h"
- #include "device/gamepad/gamepad_id_list.h"
- #include "device/gamepad/hid_haptic_gamepad.h"
- #include "device/gamepad/hid_writer_mac.h"
- #include "device/gamepad/xbox_hid_controller.h"
- namespace device {
- namespace {
- // http://www.usb.org/developers/hidpage
- const uint16_t kGenericDesktopUsagePage = 0x01;
- const uint16_t kGameControlsUsagePage = 0x05;
- const uint16_t kButtonUsagePage = 0x09;
- const uint16_t kConsumerUsagePage = 0x0c;
- const uint16_t kJoystickUsageNumber = 0x04;
- const uint16_t kGameUsageNumber = 0x05;
- const uint16_t kMultiAxisUsageNumber = 0x08;
- const uint16_t kAxisMinimumUsageNumber = 0x30;
- const uint16_t kSystemMainMenuUsageNumber = 0x85;
- const uint16_t kPowerUsageNumber = 0x30;
- const uint16_t kSearchUsageNumber = 0x0221;
- const uint16_t kHomeUsageNumber = 0x0223;
- const uint16_t kBackUsageNumber = 0x0224;
- const uint16_t kRecordUsageNumber = 0xb2;
- const int kRumbleMagnitudeMax = 10000;
- struct SpecialUsages {
- const uint16_t usage_page;
- const uint16_t usage;
- } kSpecialUsages[] = {
- // Xbox One S pre-FW update reports Xbox button as SystemMainMenu over BT.
- {kGenericDesktopUsagePage, kSystemMainMenuUsageNumber},
- // Power is used for the Guide button on the Nvidia Shield 2015 gamepad.
- {kConsumerUsagePage, kPowerUsageNumber},
- // Search is used for the Guide button on the Nvidia Shield 2017 gamepad.
- {kConsumerUsagePage, kSearchUsageNumber},
- // Start, Back, and Guide buttons are often reported as Consumer Home or
- // Back.
- {kConsumerUsagePage, kHomeUsageNumber},
- {kConsumerUsagePage, kBackUsageNumber},
- {kConsumerUsagePage, kRecordUsageNumber},
- };
- const size_t kSpecialUsagesLen = std::size(kSpecialUsages);
- float NormalizeAxis(CFIndex value, CFIndex min, CFIndex max) {
- return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
- }
- float NormalizeUInt8Axis(uint8_t value, uint8_t min, uint8_t max) {
- return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
- }
- float NormalizeUInt16Axis(uint16_t value, uint16_t min, uint16_t max) {
- return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
- }
- float NormalizeUInt32Axis(uint32_t value, uint32_t min, uint32_t max) {
- return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
- }
- GamepadBusType QueryBusType(IOHIDDeviceRef device) {
- CFStringRef transport_cf = base::mac::CFCast<CFStringRef>(
- IOHIDDeviceGetProperty(device, CFSTR(kIOHIDTransportKey)));
- if (transport_cf) {
- std::string transport = base::SysCFStringRefToUTF8(transport_cf);
- if (transport == kIOHIDTransportUSBValue)
- return GAMEPAD_BUS_USB;
- if (transport == kIOHIDTransportBluetoothValue ||
- transport == kIOHIDTransportBluetoothLowEnergyValue) {
- return GAMEPAD_BUS_BLUETOOTH;
- }
- }
- return GAMEPAD_BUS_UNKNOWN;
- }
- } // namespace
- GamepadDeviceMac::GamepadDeviceMac(int location_id,
- IOHIDDeviceRef device_ref,
- base::StringPiece product_name,
- int vendor_id,
- int product_id)
- : location_id_(location_id),
- device_ref_(device_ref),
- bus_type_(QueryBusType(device_ref_)),
- ff_device_ref_(nullptr),
- ff_effect_ref_(nullptr) {
- auto gamepad_id =
- GamepadIdList::Get().GetGamepadId(product_name, vendor_id, product_id);
- if (Dualshock4Controller::IsDualshock4(gamepad_id)) {
- dualshock4_ = std::make_unique<Dualshock4Controller>(
- gamepad_id, bus_type_, std::make_unique<HidWriterMac>(device_ref));
- return;
- }
- if (XboxHidController::IsXboxHid(gamepad_id)) {
- xbox_hid_ = std::make_unique<XboxHidController>(
- std::make_unique<HidWriterMac>(device_ref));
- return;
- }
- if (HidHapticGamepad::IsHidHaptic(vendor_id, product_id)) {
- hid_haptics_ = HidHapticGamepad::Create(
- vendor_id, product_id, std::make_unique<HidWriterMac>(device_ref));
- return;
- }
- if (device_ref) {
- ff_device_ref_ = CreateForceFeedbackDevice(device_ref);
- if (ff_device_ref_) {
- ff_effect_ref_ = CreateForceFeedbackEffect(ff_device_ref_, &ff_effect_,
- &ff_custom_force_, force_data_,
- axes_data_, direction_data_);
- }
- }
- }
- GamepadDeviceMac::~GamepadDeviceMac() = default;
- void GamepadDeviceMac::DoShutdown() {
- if (ff_device_ref_) {
- if (ff_effect_ref_) {
- FFDeviceReleaseEffect(ff_device_ref_, ff_effect_ref_);
- ff_effect_ref_ = nullptr;
- }
- FFReleaseDevice(ff_device_ref_);
- ff_device_ref_ = nullptr;
- }
- if (dualshock4_)
- dualshock4_->Shutdown();
- dualshock4_.reset();
- if (xbox_hid_)
- xbox_hid_->Shutdown();
- xbox_hid_.reset();
- if (hid_haptics_)
- hid_haptics_->Shutdown();
- hid_haptics_.reset();
- }
- // static
- bool GamepadDeviceMac::CheckCollection(IOHIDElementRef element) {
- // Check that a parent collection of this element matches one of the usage
- // numbers that we are looking for.
- while ((element = IOHIDElementGetParent(element)) != nullptr) {
- uint32_t usage_page = IOHIDElementGetUsagePage(element);
- uint32_t usage = IOHIDElementGetUsage(element);
- if (usage_page == kGenericDesktopUsagePage) {
- if (usage == kJoystickUsageNumber || usage == kGameUsageNumber ||
- usage == kMultiAxisUsageNumber) {
- return true;
- }
- }
- }
- return false;
- }
- bool GamepadDeviceMac::AddButtonsAndAxes(Gamepad* gamepad) {
- bool has_buttons = AddButtons(gamepad);
- bool has_axes = AddAxes(gamepad);
- gamepad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
- return (has_buttons || has_axes);
- }
- bool GamepadDeviceMac::AddButtons(Gamepad* gamepad) {
- base::ScopedCFTypeRef<CFArrayRef> elements_cf(IOHIDDeviceCopyMatchingElements(
- device_ref_, nullptr, kIOHIDOptionsTypeNone));
- NSArray* elements = base::mac::CFToNSCast(elements_cf);
- DCHECK(elements);
- DCHECK(gamepad);
- memset(gamepad->buttons, 0, sizeof(gamepad->buttons));
- std::fill(button_elements_, button_elements_ + Gamepad::kButtonsLengthCap,
- nullptr);
- std::vector<IOHIDElementRef> special_element(kSpecialUsagesLen, nullptr);
- size_t button_count = 0;
- size_t unmapped_button_count = 0;
- for (id elem in elements) {
- IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
- if (!CheckCollection(element))
- continue;
- uint32_t usage_page = IOHIDElementGetUsagePage(element);
- uint32_t usage = IOHIDElementGetUsage(element);
- if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button) {
- if (usage_page == kButtonUsagePage && usage > 0) {
- size_t button_index = size_t{usage - 1};
- // Ignore buttons with large usage values.
- if (button_index >= Gamepad::kButtonsLengthCap)
- continue;
- // Button index already assigned, ignore.
- if (button_elements_[button_index])
- continue;
- button_elements_[button_index] = element;
- gamepad->buttons[button_index].used = true;
- button_count = std::max(button_count, button_index + 1);
- } else {
- // Check for common gamepad buttons that are not on the Button usage
- // page. Button indices are assigned in a second pass.
- for (size_t special_index = 0; special_index < kSpecialUsagesLen;
- ++special_index) {
- const auto& special = kSpecialUsages[special_index];
- if (usage_page == special.usage_page && usage == special.usage) {
- special_element[special_index] = element;
- ++unmapped_button_count;
- }
- }
- }
- }
- }
- if (unmapped_button_count > 0) {
- // Insert unmapped buttons at unused button indices.
- size_t button_index = 0;
- for (size_t special_index = 0; special_index < kSpecialUsagesLen;
- ++special_index) {
- if (!special_element[special_index])
- continue;
- // Advance to the next unused button index.
- while (button_index < Gamepad::kButtonsLengthCap &&
- button_elements_[button_index]) {
- ++button_index;
- }
- if (button_index >= Gamepad::kButtonsLengthCap)
- break;
- button_elements_[button_index] = special_element[special_index];
- gamepad->buttons[button_index].used = true;
- button_count = std::max(button_count, button_index + 1);
- if (--unmapped_button_count == 0)
- break;
- }
- }
- gamepad->buttons_length = button_count;
- return gamepad->buttons_length > 0;
- }
- bool GamepadDeviceMac::AddAxes(Gamepad* gamepad) {
- base::ScopedCFTypeRef<CFArrayRef> elements_cf(IOHIDDeviceCopyMatchingElements(
- device_ref_, nullptr, kIOHIDOptionsTypeNone));
- NSArray* elements = base::mac::CFToNSCast(elements_cf);
- DCHECK(elements);
- DCHECK(gamepad);
- memset(gamepad->axes, 0, sizeof(gamepad->axes));
- std::fill(axis_elements_, axis_elements_ + Gamepad::kAxesLengthCap, nullptr);
- std::fill(axis_minimums_, axis_minimums_ + Gamepad::kAxesLengthCap, 0);
- std::fill(axis_maximums_, axis_maximums_ + Gamepad::kAxesLengthCap, 0);
- std::fill(axis_report_sizes_, axis_report_sizes_ + Gamepad::kAxesLengthCap,
- 0);
- // Most axes are mapped so that their index in the Gamepad axes array
- // corresponds to the usage ID. However, this is not possible when the usage
- // ID would cause the axis index to exceed the bounds of the axes array.
- // Axes with large usage IDs are mapped in a second pass.
- size_t axis_count = 0;
- size_t unmapped_axis_count = 0;
- for (id elem in elements) {
- IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
- if (!CheckCollection(element))
- continue;
- uint32_t usage_page = IOHIDElementGetUsagePage(element);
- uint32_t usage = IOHIDElementGetUsage(element);
- if (IOHIDElementGetType(element) != kIOHIDElementTypeInput_Misc ||
- usage < kAxisMinimumUsageNumber) {
- continue;
- }
- size_t axis_index = size_t{usage - kAxisMinimumUsageNumber};
- if (axis_index < Gamepad::kAxesLengthCap) {
- // Axis index already assigned, ignore.
- if (axis_elements_[axis_index])
- continue;
- axis_elements_[axis_index] = element;
- axis_count = std::max(axis_count, axis_index + 1);
- } else if (usage_page <= kGameControlsUsagePage) {
- // Assign an index for this axis in the second pass.
- ++unmapped_axis_count;
- }
- }
- if (unmapped_axis_count > 0) {
- // Insert unmapped axes at unused axis indices.
- size_t axis_index = 0;
- for (id elem in elements) {
- IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
- if (!CheckCollection(element))
- continue;
- uint32_t usage_page = IOHIDElementGetUsagePage(element);
- uint32_t usage = IOHIDElementGetUsage(element);
- if (IOHIDElementGetType(element) != kIOHIDElementTypeInput_Misc ||
- usage < kAxisMinimumUsageNumber ||
- usage_page > kGameControlsUsagePage) {
- continue;
- }
- // Ignore axes with small usage IDs that should have been mapped in the
- // initial pass.
- if (size_t{usage - kAxisMinimumUsageNumber} < Gamepad::kAxesLengthCap)
- continue;
- // Advance to the next unused axis index.
- while (axis_index < Gamepad::kAxesLengthCap &&
- axis_elements_[axis_index]) {
- ++axis_index;
- }
- if (axis_index >= Gamepad::kAxesLengthCap)
- break;
- axis_elements_[axis_index] = element;
- axis_count = std::max(axis_count, axis_index + 1);
- if (--unmapped_axis_count == 0)
- break;
- }
- }
- // Fetch the logical range and report size for each axis.
- for (size_t axis_index = 0; axis_index < axis_count; ++axis_index) {
- IOHIDElementRef element = axis_elements_[axis_index];
- if (element != nullptr) {
- CFIndex axis_min = IOHIDElementGetLogicalMin(element);
- CFIndex axis_max = IOHIDElementGetLogicalMax(element);
- // Some HID axes report a logical range of -1 to 0 signed, which must be
- // interpreted as 0 to -1 unsigned for correct normalization behavior.
- if (axis_min == -1 && axis_max == 0) {
- axis_max = -1;
- axis_min = 0;
- }
- axis_minimums_[axis_index] = axis_min;
- axis_maximums_[axis_index] = axis_max;
- axis_report_sizes_[axis_index] = IOHIDElementGetReportSize(element);
- gamepad->axes_used |= 1 << axis_index;
- }
- }
- gamepad->axes_length = axis_count;
- return gamepad->axes_length > 0;
- }
- void GamepadDeviceMac::UpdateGamepadForValue(IOHIDValueRef value,
- Gamepad* gamepad) {
- DCHECK(gamepad);
- IOHIDElementRef element = IOHIDValueGetElement(value);
- uint32_t value_length = IOHIDValueGetLength(value);
- if (dualshock4_) {
- // Handle Dualshock4 input reports that do not specify HID gamepad usages
- // in the report descriptor.
- uint32_t report_id = IOHIDElementGetReportID(element);
- auto report = base::make_span(IOHIDValueGetBytePtr(value), value_length);
- if (dualshock4_->ProcessInputReport(report_id, report, gamepad))
- return;
- }
- // Values larger than 4 bytes cannot be handled by IOHIDValueGetIntegerValue.
- if (value_length > 4)
- return;
- // Find and fill in the associated button event, if any.
- for (size_t i = 0; i < gamepad->buttons_length; ++i) {
- if (button_elements_[i] == element) {
- bool pressed = IOHIDValueGetIntegerValue(value);
- gamepad->buttons[i].pressed = pressed;
- gamepad->buttons[i].value = pressed ? 1.f : 0.f;
- gamepad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
- return;
- }
- }
- // Find and fill in the associated axis event, if any.
- for (size_t i = 0; i < gamepad->axes_length; ++i) {
- if (axis_elements_[i] == element) {
- CFIndex axis_min = axis_minimums_[i];
- CFIndex axis_max = axis_maximums_[i];
- CFIndex axis_value = IOHIDValueGetIntegerValue(value);
- if (axis_min > axis_max) {
- // We'll need to interpret this axis as unsigned during normalization.
- switch (axis_report_sizes_[i]) {
- case 8:
- gamepad->axes[i] =
- NormalizeUInt8Axis(axis_value, axis_min, axis_max);
- break;
- case 16:
- gamepad->axes[i] =
- NormalizeUInt16Axis(axis_value, axis_min, axis_max);
- break;
- case 32:
- gamepad->axes[i] =
- NormalizeUInt32Axis(axis_value, axis_min, axis_max);
- break;
- }
- } else {
- gamepad->axes[i] = NormalizeAxis(axis_value, axis_min, axis_max);
- }
- gamepad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
- return;
- }
- }
- }
- bool GamepadDeviceMac::SupportsVibration() {
- return dualshock4_ || xbox_hid_ || hid_haptics_ || ff_device_ref_;
- }
- void GamepadDeviceMac::SetVibration(mojom::GamepadEffectParametersPtr params) {
- if (dualshock4_) {
- dualshock4_->SetVibration(std::move(params));
- return;
- }
- if (xbox_hid_) {
- xbox_hid_->SetVibration(std::move(params));
- return;
- }
- if (hid_haptics_) {
- hid_haptics_->SetVibration(std::move(params));
- return;
- }
- if (ff_device_ref_) {
- FFCUSTOMFORCE* ff_custom_force =
- static_cast<FFCUSTOMFORCE*>(ff_effect_.lpvTypeSpecificParams);
- DCHECK(ff_custom_force);
- DCHECK(ff_custom_force->rglForceData);
- ff_custom_force->rglForceData[0] =
- static_cast<LONG>(params->strong_magnitude * kRumbleMagnitudeMax);
- ff_custom_force->rglForceData[1] =
- static_cast<LONG>(params->weak_magnitude * kRumbleMagnitudeMax);
- // Download the effect to the device and start the effect.
- HRESULT res = FFEffectSetParameters(
- ff_effect_ref_, &ff_effect_,
- FFEP_DURATION | FFEP_STARTDELAY | FFEP_TYPESPECIFICPARAMS);
- if (res == FF_OK)
- FFEffectStart(ff_effect_ref_, 1, FFES_SOLO);
- }
- }
- void GamepadDeviceMac::SetZeroVibration() {
- if (dualshock4_) {
- dualshock4_->SetZeroVibration();
- return;
- }
- if (xbox_hid_) {
- xbox_hid_->SetZeroVibration();
- return;
- }
- if (hid_haptics_) {
- hid_haptics_->SetZeroVibration();
- return;
- }
- if (ff_effect_ref_)
- FFEffectStop(ff_effect_ref_);
- }
- // static
- FFDeviceObjectReference GamepadDeviceMac::CreateForceFeedbackDevice(
- IOHIDDeviceRef device_ref) {
- io_service_t service = IOHIDDeviceGetService(device_ref);
- if (service == MACH_PORT_NULL)
- return nullptr;
- HRESULT res = FFIsForceFeedback(service);
- if (res != FF_OK)
- return nullptr;
- FFDeviceObjectReference ff_device_ref;
- res = FFCreateDevice(service, &ff_device_ref);
- if (res != FF_OK)
- return nullptr;
- return ff_device_ref;
- }
- // static
- FFEffectObjectReference GamepadDeviceMac::CreateForceFeedbackEffect(
- FFDeviceObjectReference ff_device_ref,
- FFEFFECT* ff_effect,
- FFCUSTOMFORCE* ff_custom_force,
- LONG* force_data,
- DWORD* axes_data,
- LONG* direction_data) {
- DCHECK(ff_effect);
- DCHECK(ff_custom_force);
- DCHECK(force_data);
- DCHECK(axes_data);
- DCHECK(direction_data);
- FFCAPABILITIES caps;
- HRESULT res = FFDeviceGetForceFeedbackCapabilities(ff_device_ref, &caps);
- if (res != FF_OK)
- return nullptr;
- if ((caps.supportedEffects & FFCAP_ET_CUSTOMFORCE) == 0)
- return nullptr;
- force_data[0] = 0;
- force_data[1] = 0;
- axes_data[0] = caps.ffAxes[0];
- axes_data[1] = caps.ffAxes[1];
- direction_data[0] = 0;
- direction_data[1] = 0;
- ff_custom_force->cChannels = 2;
- ff_custom_force->cSamples = 2;
- ff_custom_force->rglForceData = force_data;
- ff_custom_force->dwSamplePeriod = 100000; // 100 ms
- ff_effect->dwSize = sizeof(FFEFFECT);
- ff_effect->dwFlags = FFEFF_OBJECTOFFSETS | FFEFF_SPHERICAL;
- ff_effect->dwDuration = 5000000; // 5 seconds
- ff_effect->dwSamplePeriod = 100000; // 100 ms
- ff_effect->dwGain = 10000;
- ff_effect->dwTriggerButton = FFEB_NOTRIGGER;
- ff_effect->dwTriggerRepeatInterval = 0;
- ff_effect->cAxes = caps.numFfAxes;
- ff_effect->rgdwAxes = axes_data;
- ff_effect->rglDirection = direction_data;
- ff_effect->lpEnvelope = nullptr;
- ff_effect->cbTypeSpecificParams = sizeof(FFCUSTOMFORCE);
- ff_effect->lpvTypeSpecificParams = ff_custom_force;
- ff_effect->dwStartDelay = 0;
- FFEffectObjectReference ff_effect_ref;
- res = FFDeviceCreateEffect(ff_device_ref, kFFEffectType_CustomForce_ID,
- ff_effect, &ff_effect_ref);
- if (res != FF_OK)
- return nullptr;
- return ff_effect_ref;
- }
- base::WeakPtr<AbstractHapticGamepad> GamepadDeviceMac::GetWeakPtr() {
- return weak_factory_.GetWeakPtr();
- }
- } // namespace device
|