mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 22:09:19 -07:00
79a646a67d
On OSX, iterate the HID device's elements and only store the last of each type to accommodate for flaky hardware
306 lines
7.2 KiB
Plaintext
306 lines
7.2 KiB
Plaintext
// 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;
|
|
}
|
|
}
|
|
}
|