Sign in to follow this  
the_edd

[Mac OS X] help detecting joysticks with IOKit

Recommended Posts

the_edd    2109
I'm having trouble communicating with my PS3 controller via the IOKit APIs.

For some reason, I'm being told that it has 314 interface elements! (buttons, axes, etc). Elements 0-25 are picked up correctly as far as I can tell and I am able to poll them in order to obtain their states. But when element 26 is accessed, my little test application dies. Presumably elements 26 and above are fictional as 25 elements sounds about right to me, but I'm not sure what to do about it, or how to fix my code.

I've tried another gamepad (PS2 controller via a USB adapter) and that works fine. The problems with the PS3 pad occur when it is connected either via USB or via Bluetooth.

Code to the console application is posted below. Hopefully I've just goofed in some way I can't see right now... Any ideas?

[code]
// /XCode3/usr/bin/g++-4.0 -isysroot /XCode3/SDKs/MacOSX10.5.sdk/ joysticks.cpp -o joysticks -W -Wall -ansi -pedantic -framework CoreFoundation -framework IOKit -mmacosx-version-min=10.5

#include <IOKit/hid/IOHIDManager.h>
#include <CoreFoundation/CoreFoundation.h>

#include <cassert>
#include <stdexcept>
#include <new>
#include <iostream>
#include <iomanip>
#include <vector>

// Some RAII stuff...

template<typename Ref>
struct scoped_CF
{
public:
scoped_CF(Ref r) : r(r) { }
~scoped_CF() { if (r) CFRelease(r); }

operator Ref () const { return r; }
Ref get() const { return r; }

private:
scoped_CF(const scoped_CF &);
scoped_CF &operator= (const scoped_CF &);

Ref r;
};

struct HID_manager_closer
{
HID_manager_closer(IOHIDManagerRef man) : man(man) { }
~HID_manager_closer() { IOHIDManagerClose(man, kIOHIDOptionsTypeNone); }

IOHIDManagerRef man;
};

bool get_device_string(IOHIDDeviceRef dev, CFStringRef property, char (&buff)[256])
{
CFTypeRef ref = IOHIDDeviceGetProperty(dev, property);
if (ref && CFGetTypeID(ref) == CFStringGetTypeID())
{
CFStringRef s = static_cast<CFStringRef>(ref);
CFStringGetCString(s, buff, sizeof buff, kCFStringEncodingUTF8);
// TODO: should really check enough space was present, use dynamically allocated block, yadda yadda
return true;
}

return false;
}

void dump_element(IOHIDDeviceRef dev, IOHIDElementRef element)
{
//CFStringRef nameref = IOHIDElementGetName(e); // useless

const char *other = "other";
const IOHIDElementType type = IOHIDElementGetType(element);
const char *typestr = (type == kIOHIDElementTypeInput_Button) ? "button" :
(type == kIOHIDElementTypeInput_Axis) ? "axis" :
(type == kIOHIDElementTypeInput_Misc) ? "ranged input" :
other;

std::cout << "-> found " << std::setw(12) << typestr << " -- ";
if (typestr == other)
{
std::cout << " ???\n";
}
else
{
std::cout << " min: " << std::left << std::setw(6) << IOHIDElementGetLogicalMin(element) <<
" max: " << std::setw(6) << IOHIDElementGetLogicalMax(element);

IOHIDValueRef valref = 0;
if (IOHIDDeviceGetValue(dev, element, &valref) == kIOReturnSuccess)
{
assert(valref);
std::cout << " now: " << std::setw(6) << IOHIDValueGetIntegerValue(valref) << '\n';
}
else
std::cout << '\n';
}
}

void dump_device(IOHIDDeviceRef dev, unsigned n)
{
char product[256] = "Unknown product";
get_device_string(dev, CFSTR(kIOHIDProductKey), product);

char manufacturer[256] = "Unknown manufacturer";
get_device_string(dev, CFSTR("kIOHIDManufacturerKey"), manufacturer);

std::cout << "#" << n << ": " << product << " (" << manufacturer << ")\n";

scoped_CF<CFArrayRef> elements(IOHIDDeviceCopyMatchingElements(dev, 0, kIOHIDOptionsTypeNone));
std::cout << std::left;
if (elements)
{
for (std::size_t i = 0, n = CFArrayGetCount(elements); i != n; ++i)
{
CFTypeRef tref = CFArrayGetValueAtIndex(elements, i);
assert(tref);
assert(CFGetTypeID(tref) == IOHIDElementGetTypeID());
IOHIDElementRef e = static_cast<IOHIDElementRef>(const_cast<void *>(tref));
dump_element(dev, e);
}
}

std::cout << "\n";
}

unsigned dump_devices(UInt32 usage)
{
scoped_CF<IOHIDManagerRef> manager(IOHIDManagerCreate(CFAllocatorGetDefault(), 0 /* reserved */));
if (!manager) throw std::bad_alloc();
assert(CFGetTypeID(manager) == IOHIDManagerGetTypeID());

// Create a "matching dictionary", describing the HI devices we're looking for.
scoped_CF<CFMutableDictionaryRef> matcher(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)
);
if (!matcher) throw std::bad_alloc();

UInt32 page = kHIDPage_GenericDesktop;

// Add key for device type to matching dicionary
scoped_CF<CFNumberRef> pageref(CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page));
if (!pageref) throw std::bad_alloc();
CFDictionarySetValue(matcher, CFSTR(kIOHIDDeviceUsagePageKey), pageref);

scoped_CF<CFNumberRef> usageref(CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage));
if (!usageref) throw std::bad_alloc();
CFDictionarySetValue(matcher, CFSTR(kIOHIDDeviceUsageKey), usageref);

IOHIDManagerSetDeviceMatching(manager, matcher);

// Could register callbacks at this point for device connection/disconnection, but would require a
// CFRunLoop in order to process events, so we'll skip that for now.
// See IOHIDManagerRegisterDeviceMatchingCallback and IOHIDManagerRegisterDeviceRemovalCallback.

// Open the HID manager.
if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess)
throw std::runtime_error("failed to open HID manager");

HID_manager_closer mancloser(manager);

// Get a set containing the matching devices.
// Returns null rather than empty set when there are no matching devices
scoped_CF<CFSetRef> deviceset(IOHIDManagerCopyDevices(manager));
unsigned ret = 0;
if (deviceset)
{
std::vector<const void *> devices(CFSetGetCount(deviceset), 0);
if (!devices.empty())
{
ret = devices.size();
CFSetGetValues(deviceset, &devices.front());

for (std::size_t i = 0; i != ret; ++i)
{
assert(devices[i] != 0);
dump_device(static_cast<IOHIDDeviceRef>(const_cast<void *>(devices[i])), i);
}
}
}

return ret;
}

int main()
{
std::cout << "Joysticks:\n";
std::cout << "----------\n";
if (dump_devices(kHIDUsage_GD_Joystick) == 0)
std::cout << "None\n\n";

std::cout << "Gamepads:\n";
std::cout << "---------\n";
if (dump_devices(kHIDUsage_GD_GamePad) == 0)
std::cout << "None\n\n";

return 0;
}
[/code]

Share this post


Link to post
Share on other sites
the_edd    2109
Just for people arriving here via google:

I found some code on the web that alluded to having encountered the same problem. The workaround is to add a call to IOHIDValueGetLength() and check it returns a byte count no greater than sizeof(CFIndex) before making the call to IOHIDValueGetIntegerValue().

I'm still being told there are 314 elements on the device, but at least the problematic ones can be skipped.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this