#include "config.h"
#include "HIDGamepad.h"
#if ENABLE(GAMEPAD)
#include <IOKit/hid/IOHIDElement.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/hid/IOHIDValue.h>
#include <wtf/CurrentTime.h>
#include <wtf/text/CString.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
HIDGamepad::HIDGamepad(IOHIDDeviceRef hidDevice, unsigned index)
: PlatformGamepad(index)
, m_hidDevice(hidDevice)
{
m_connectTime = m_lastUpdateTime = monotonicallyIncreasingTime();
CFNumberRef cfVendorID = (CFNumberRef)IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
CFNumberRef cfProductID = (CFNumberRef)IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
int vendorID, productID;
CFNumberGetValue(cfVendorID, kCFNumberIntType, &vendorID);
CFNumberGetValue(cfProductID, kCFNumberIntType, &productID);
CFStringRef cfProductName = (CFStringRef)IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
String productName(cfProductName);
m_id = String::format("%x-%x-%s", vendorID, productID, productName.utf8().data());
initElements();
}
void HIDGamepad::getCurrentValueForElement(const HIDGamepadElement& gamepadElement)
{
IOHIDElementRef element = gamepadElement.iohidElement.get();
IOHIDValueRef value;
if (IOHIDDeviceGetValue(IOHIDElementGetDevice(element), element, &value) == kIOReturnSuccess)
valueChanged(value);
}
void HIDGamepad::initElements()
{
RetainPtr<CFArrayRef> elements = adoptCF(IOHIDDeviceCopyMatchingElements(m_hidDevice.get(), NULL, kIOHIDOptionsTypeNone));
initElementsFromArray(elements.get());
std::sort(m_buttons.begin(), m_buttons.end(), [](const std::unique_ptr<HIDGamepadButton>& a, const std::unique_ptr<HIDGamepadButton>& b) {
return a->priority < b->priority;
});
m_axisValues.resize(m_axes.size());
m_buttonValues.resize(m_buttons.size());
for (auto& button : m_buttons)
getCurrentValueForElement(*button);
for (auto& axis : m_axes)
getCurrentValueForElement(*axis);
}
void HIDGamepad::initElementsFromArray(CFArrayRef elements)
{
for (CFIndex i = 0, count = CFArrayGetCount(elements); i < count; ++i) {
IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
if (CFGetTypeID(element) != IOHIDElementGetTypeID())
continue;
IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
if (m_elementMap.contains(cookie))
continue;
IOHIDElementType type = IOHIDElementGetType(element);
if ((type == kIOHIDElementTypeInput_Misc || type == kIOHIDElementTypeInput_Button) && maybeAddButton(element))
continue;
if ((type == kIOHIDElementTypeInput_Misc || type == kIOHIDElementTypeInput_Axis) && maybeAddAxis(element))
continue;
if (type == kIOHIDElementTypeCollection)
initElementsFromArray(IOHIDElementGetChildren(element));
}
}
bool HIDGamepad::maybeAddButton(IOHIDElementRef element)
{
uint32_t usagePage = IOHIDElementGetUsagePage(element);
if (usagePage != kHIDPage_Button)
return false;
uint32_t usage = IOHIDElementGetUsage(element);
if (!usage)
return false;
CFIndex min = IOHIDElementGetLogicalMin(element);
CFIndex max = IOHIDElementGetLogicalMax(element);
m_buttons.append(std::make_unique<HIDGamepadButton>(usage, min, max, element));
IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
m_elementMap.set(cookie, m_buttons.last().get());
return true;
}
bool HIDGamepad::maybeAddAxis(IOHIDElementRef element)
{
uint32_t usagePage = IOHIDElementGetUsagePage(element);
if (usagePage != kHIDPage_GenericDesktop)
return false;
uint32_t usage = IOHIDElementGetUsage(element);
if (usage < kHIDUsage_GD_X || usage > kHIDUsage_GD_Rz)
return false;
CFIndex min = IOHIDElementGetPhysicalMin(element);
CFIndex max = IOHIDElementGetPhysicalMax(element);
m_axes.append(std::make_unique<HIDGamepadAxis>(min, max, element));
IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
m_elementMap.set(cookie, m_axes.last().get());
return true;
}
void HIDGamepad::valueChanged(IOHIDValueRef value)
{
IOHIDElementCookie cookie = IOHIDElementGetCookie(IOHIDValueGetElement(value));
HIDGamepadElement* element = m_elementMap.get(cookie);
if (!element)
return;
element->rawValue = IOHIDValueGetScaledValue(value, kIOHIDValueScaleTypePhysical);
if (element->isButton()) {
for (unsigned i = 0; i < m_buttons.size(); ++i) {
if (m_buttons[i].get() == element)
m_buttonValues[i] = element->normalizedValue();
}
} else if (element->isAxis()) {
for (unsigned i = 0; i < m_axes.size(); ++i) {
if (m_axes[i].get() == element)
m_axisValues[i] = element->normalizedValue();
}
} else
ASSERT_NOT_REACHED();
m_lastUpdateTime = monotonicallyIncreasingTime();
}
}
#endif // ENABLE(GAMEPAD)