dolphin/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm
Arthur Carlsson 79a646a67d Prevent multiple HID elements of same usage type on OSX
On OSX, iterate the HID device's elements and only store the last of
each type to accommodate for flaky hardware
2017-09-15 19:19:46 +02:00

305 lines
7.2 KiB
Text

// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <sstream>
#include <Foundation/Foundation.h>
#include <IOKit/hid/IOHIDLib.h>
#include "Common/StringUtil.h"
#include "InputCommon/ControllerInterface/OSX/OSXJoystick.h"
namespace ciface
{
namespace OSX
{
Joystick::Joystick(IOHIDDeviceRef device, std::string name)
: m_device(device), m_device_name(name), m_ff_device(nullptr)
{
// Buttons
NSDictionary* buttonDict = @{
@kIOHIDElementTypeKey : @(kIOHIDElementTypeInput_Button),
@kIOHIDElementUsagePageKey : @(kHIDPage_Button)
};
CFArrayRef buttons =
IOHIDDeviceCopyMatchingElements(m_device, (CFDictionaryRef)buttonDict, kIOHIDOptionsTypeNone);
if (buttons)
{
for (int i = 0; i < CFArrayGetCount(buttons); i++)
{
IOHIDElementRef e = (IOHIDElementRef)CFArrayGetValueAtIndex(buttons, i);
// DeviceElementDebugPrint(e, nullptr);
AddInput(new Button(e, m_device));
}
CFRelease(buttons);
}
// Axes
NSDictionary* axisDict = @{
@kIOHIDElementTypeKey : @(kIOHIDElementTypeInput_Misc),
@kIOHIDElementUsagePageKey : @(kHIDPage_GenericDesktop)
};
CFArrayRef axes =
IOHIDDeviceCopyMatchingElements(m_device, (CFDictionaryRef)axisDict, kIOHIDOptionsTypeNone);
if (axes)
{
std::vector<IOHIDElementRef> elems;
for (int i = 0; i < CFArrayGetCount(axes); i++)
{
IOHIDElementRef e = (IOHIDElementRef)CFArrayGetValueAtIndex(axes, i);
// DeviceElementDebugPrint(e, nullptr);
uint32_t usage = IOHIDElementGetUsage(e);
// Check for any existing elements with the same usage
auto it = std::find_if(elems.begin(), elems.end(), [usage](const auto& ref) {
return usage == IOHIDElementGetUsage(ref);
});
if (it == elems.end())
elems.push_back(e);
else
*it = e;
}
for (auto e : elems)
{
if (IOHIDElementGetUsage(e) == kHIDUsage_GD_Hatswitch)
{
AddInput(new Hat(e, m_device, Hat::up));
AddInput(new Hat(e, m_device, Hat::right));
AddInput(new Hat(e, m_device, Hat::down));
AddInput(new Hat(e, m_device, Hat::left));
}
else
{
AddAnalogInputs(new Axis(e, m_device, Axis::negative),
new Axis(e, m_device, Axis::positive));
}
}
CFRelease(axes);
}
// Force Feedback
FFCAPABILITIES ff_caps;
if (SUCCEEDED(
ForceFeedback::FFDeviceAdapter::Create(IOHIDDeviceGetService(m_device), &m_ff_device)) &&
SUCCEEDED(FFDeviceGetForceFeedbackCapabilities(m_ff_device->m_device, &ff_caps)))
{
InitForceFeedback(m_ff_device, ff_caps.numFfAxes);
}
}
Joystick::~Joystick()
{
if (m_ff_device)
m_ff_device->Release();
}
std::string Joystick::GetName() const
{
return m_device_name;
}
std::string Joystick::GetSource() const
{
return "Input";
}
ControlState Joystick::Button::GetState() const
{
IOHIDValueRef value;
if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess)
return IOHIDValueGetIntegerValue(value);
else
return 0;
}
std::string Joystick::Button::GetName() const
{
std::ostringstream s;
s << IOHIDElementGetUsage(m_element);
return std::string("Button ") + StripSpaces(s.str());
}
Joystick::Axis::Axis(IOHIDElementRef element, IOHIDDeviceRef device, direction dir)
: m_element(element), m_device(device), m_direction(dir)
{
// Need to parse the element a bit first
std::string description("unk");
int const usage = IOHIDElementGetUsage(m_element);
switch (usage)
{
case kHIDUsage_GD_X:
description = "X";
break;
case kHIDUsage_GD_Y:
description = "Y";
break;
case kHIDUsage_GD_Z:
description = "Z";
break;
case kHIDUsage_GD_Rx:
description = "Rx";
break;
case kHIDUsage_GD_Ry:
description = "Ry";
break;
case kHIDUsage_GD_Rz:
description = "Rz";
break;
case kHIDUsage_GD_Wheel:
description = "Wheel";
break;
case kHIDUsage_Csmr_ACPan:
description = "Pan";
break;
default:
{
IOHIDElementCookie elementCookie = IOHIDElementGetCookie(m_element);
// This axis isn't a 'well-known' one so cook a descriptive and uniquely
// identifiable name. macOS provides a 'cookie' for each element that
// will persist between sessions and identify the same physical controller
// element so we can use that as a component of the axis name
std::ostringstream s;
s << "CK-";
s << elementCookie;
description = StripSpaces(s.str());
break;
}
}
m_name = std::string("Axis ") + description;
m_name.append((m_direction == positive) ? "+" : "-");
m_neutral = (IOHIDElementGetLogicalMax(m_element) + IOHIDElementGetLogicalMin(m_element)) / 2.;
m_scale = 1 / fabs(IOHIDElementGetLogicalMax(m_element) - m_neutral);
}
ControlState Joystick::Axis::GetState() const
{
IOHIDValueRef value;
if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess)
{
// IOHIDValueGetIntegerValue() crashes when trying
// to convert unusually large element values.
if (IOHIDValueGetLength(value) > 2)
return 0;
float position = IOHIDValueGetIntegerValue(value);
if (m_direction == positive && position > m_neutral)
return (position - m_neutral) * m_scale;
if (m_direction == negative && position < m_neutral)
return (m_neutral - position) * m_scale;
}
return 0;
}
std::string Joystick::Axis::GetName() const
{
return m_name;
}
Joystick::Hat::Hat(IOHIDElementRef element, IOHIDDeviceRef device, direction dir)
: m_element(element), m_device(device), m_direction(dir)
{
switch (dir)
{
case up:
m_name = "Up";
break;
case right:
m_name = "Right";
break;
case down:
m_name = "Down";
break;
case left:
m_name = "Left";
break;
default:
m_name = "unk";
}
}
ControlState Joystick::Hat::GetState() const
{
IOHIDValueRef value;
if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess)
{
int position = IOHIDValueGetIntegerValue(value);
int min = IOHIDElementGetLogicalMin(m_element);
int max = IOHIDElementGetLogicalMax(m_element);
// if the position is outside the min or max, don't register it as a valid button press
if (position < min || position > max)
{
return 0;
}
// normalize the position so that its lowest value is 0
position -= min;
switch (position)
{
case 0:
if (m_direction == up)
return 1;
break;
case 1:
if (m_direction == up || m_direction == right)
return 1;
break;
case 2:
if (m_direction == right)
return 1;
break;
case 3:
if (m_direction == right || m_direction == down)
return 1;
break;
case 4:
if (m_direction == down)
return 1;
break;
case 5:
if (m_direction == down || m_direction == left)
return 1;
break;
case 6:
if (m_direction == left)
return 1;
break;
case 7:
if (m_direction == left || m_direction == up)
return 1;
break;
};
}
return 0;
}
std::string Joystick::Hat::GetName() const
{
return m_name;
}
bool Joystick::IsSameDevice(const IOHIDDeviceRef other_device) const
{
return m_device == other_device;
}
}
}