Tonight I began the process of writing some using the HID APIs to be able to use an XBox 360 controller on my MacBook Pro for the Athena engine.
As usual, my Courage game played host to testing this new method of control.
First of all, the XBox 360 pad uses a 0xff device class (Vendor specific), so normal HID drivers (they use a device class of 0×03) will not attach to it, so I needed to download a driver for MacOSX that could handle this. Also I didn’t have a Microsoft Wireless Gaming Receiver so I bought a wired pad instead.
I tried the Tattiebogle driver, and at first it worked, but after I logged into VMWare Fusion and used by pad in Vista it stopped working on MacOSX. Or it could have been that I got the Wacom Bamboo pad installed recently.
So after a bit of searching I found another driver, which although didn’t work, sparked Tattiebogle’s to life. This one is known as XBox HID Driver for Mac OS X.
Once it was all up and running, getting code to listen for the 360 pad was a breeze, and Apple has a nice section on your website for it Technical Note TN2187: New HID Manager APIs for Mac OS X version 10. I’ll summarise the basics needed to connect to the pad.
None of this code cleans up properly, like I said I only just started with the HID manager, so I’ll probably post an example App at some point that does clean up properly when shutting down.
Setting up HID Manager
The following code is quite straight forward, it just sets up two callbacks, one for when a device matches what we are looking for, and one for when a device is removed.
The IOKit.framework is required.
I also have two global variables, one for the HID manager, and another the device we going to be reading (this code can only do one device since we only have one buffer variable).
//
//
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDUsageTables.h>
//
//
namespace HIDInput
{
//
void DeviceMatchingCallback( void * inContext, IOReturn inResult, void * inSender, IOHIDDeviceRef inIOHIDDeviceRef );
void DeviceRemovalCallback( void * inContext, IOReturn inResult, void * inSender, IOHIDDeviceRef inIOHIDDeviceRef );
void DeviceIOHIDReportCallback( void * inContext, IOReturn inResult, void * inSender, IOHIDReportType inType, uint32_t inReportID, uint8_t * inReport, CFIndex inReportLength );
Boolean Device_GetLongProperty( IOHIDDeviceRef inDeviceRef, CFStringRef inKey, long * outValue );
long Device_GetVendorID( IOHIDDeviceRef inIOHIDDeviceRef );
long Device_GetProductID( IOHIDDeviceRef inIOHIDDeviceRef );
//
IOHIDManagerRef l_IOHIDManagerRef;
uint8_t * l_pReportBuf = 0;
}
//
//
int argc, char * argv[] )
{
// Create a HID manager with a default allocator
l_IOHIDManagerRef = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone );
assert( IOHIDManagerRef );
// Look for all device and open the manager
IOHIDManagerSetDeviceMatching( l_IOHIDManagerRef, NULL );
IOHIDManagerOpen( l_IOHIDManagerRef, kIOHIDOptionsTypeNone );
// Register Callbacks to be run with application loop
IOHIDManagerRegisterDeviceMatchingCallback( l_IOHIDManagerRef, HIDInput::DeviceMatchingCallback, NULL );
IOHIDManagerRegisterDeviceRemovalCallback( l_IOHIDManagerRef, HIDInput::DeviceRemovalCallback, NULL );
IOHIDManagerScheduleWithRunLoop( l_IOHIDManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
//
while( true )
{
// loop application
}
return 0;
}
The call to IOHIDManagerSetDeviceMatching() can have a dictionary passed to it to look only for devices matching certain criteria, but for simplicity, I’ll just be checking all devices in this example, so I passed NULL as a second argument.
Checking for XBox360 Pad
With the code setup for calling back when a device is found that matches (or all devices in our case), we now need to check the device vendor id and product id to be sure they are what we are looking for.
//
//
Boolean HIDInput::Device_GetLongProperty( IOHIDDeviceRef inDeviceRef, CFStringRef inKey, long * outValue )
{
CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty( inDeviceRef, inKey );
Boolean result = FALSE;
if( tCFTypeRef )
{
// If this is a number get its value
if( CFNumberGetTypeID( ) == CFGetTypeID( tCFTypeRef ) )
{
result = CFNumberGetValue( ( CFNumberRef ) tCFTypeRef, kCFNumberSInt32Type, outValue );
}
}
return result;
}
//
//
long HIDInput::Device_GetVendorID( IOHIDDeviceRef inIOHIDDeviceRef )
{
long result = 0;
Device_GetLongProperty( inIOHIDDeviceRef, CFSTR( kIOHIDVendorIDKey ), &result );
return result;
}
//
//
long HIDInput::Device_GetProductID( IOHIDDeviceRef inIOHIDDeviceRef )
{
long result = 0;
Device_GetLongProperty( inIOHIDDeviceRef, CFSTR( kIOHIDProductIDKey ), &result );
return result;
}
//
//
void HIDInput::DeviceMatchingCallback( void * inContext, IOReturn inResult, void * inSender, IOHIDDeviceRef inIOHIDDeviceRef )
{
if( !inIOHIDDeviceRef )
{
return;
}
const long kVendor_Microsoft = 0x045e;
const long kProduct_XBox360Pad = 0x028e;
const long kVendorID = Device_GetVendorID( inIOHIDDeviceRef );
const long kProductID = Device_GetProductID( inIOHIDDeviceRef );
if( kVendorID == kVendor_Microsoft && kProductID == kProduct_XBox360Pad )
{
// Assert if we're trying to create a second XBox 360 pad device
assert( l_pReportBuf );
// Get the size of the report for the device and allocate a buffer. The
// report size property should be right, however the packet header
// seems to want 20 bytes, so I'm allocating 20 bytes to be safe.
CFIndex reportSize = 20;
//IOHIDDevice_GetLongProperty( inIOHIDDeviceRef, CFSTR( kIOHIDMaxInputReportSizeKey ), &reportSize );
l_pReportBuf = static_cast<uint8_t *>( malloc( reportSize ) );
// Register a report callback for this device
IOHIDDeviceRegisterInputReportCallback( inIOHIDDeviceRef, l_pReportBuf, reportSize, DeviceIOHIDReportCallback, 0 );
printf( "XBox Pad Connected.\n" );
}
}
//
//
void HIDInput::DeviceRemovalCallback( void * inContext, IOReturn inResult, void * inSender, IOHIDDeviceRef inIOHIDDeviceRef )
{
// Deallocate report buffer
free( l_pReportBuf );
l_pReportBuf = 0;
//
printf( "XBox Pad Disconnected.\n" );
}
We have two constants in the device matching callback, one that is the vendor id for Microsoft, and the other which is the product id for the XBox 360 pad.
Since this code only supports the one pad, I’ve added an assert which will be triggered if a second pad is attempted to be matched that is a 360 pad (it checks to see if the report buffer has already been allocated).
Once a device has been removed, I free the memory for the report buffer. I think I’m suppose to unregister the input report callback by passing null as the callback, but I’m not 100% sure yet.
All the other helper functions were found on the Apple website technical note.
Reading Pad Status
XBox 360 pad updates come through on a report type of 0×00 and are 14 bytes long (though I have read on other sites of people getting reports of 20 bytes, this just seems to be padding).
I have skipped the checking of the report type in this code, but it should probably be checked, as well as the report length.
//
//
void HIDInput::DeviceIOHIDReportCallback( void * inContext, IOReturn inResult, void * inSender, IOHIDReportType inType, uint32_t inReportID, uint8_t * inReport, CFIndex inReportLength )
{
// Output hex for each byte in the report (always 2 characters long, making up the rest with 0)
for( int i = 0; inReportLength; ++i )
{
printf( "%02x", inReport[i] );
}
//
printf( "\n" );
}
That sourcecode allows you to read just the actual reports being sent from the pad, however if you want to be able to use it as a 360 pad. You’ll get a message something like this…
0014000000002efcc70792ff9a06
The 0014 at the beginning is a packet header, first byte being the command, second being the size of the packet. The other sections are split up as follows…
0014aaaabbccddddeeeeffffgggg
a is an unsigned short of button flags
b is an unsigned char representing the left trigger (LT)
c is an unsigned char representing the right trigger (RT)
d is a signed short representing the Left Axis X
e is a signed short representing the Left Axis Y
f is a signed short representing the Right Axis X
g is a signed short representing the Right Axis Y
This can be put into a function like so…
//
//
void HIDInput::DeviceIOHIDReportCallback( void * inContext, IOReturn inResult, void * inSender, IOHIDReportType inType, uint32_t inReportID, uint8_t * inReport, CFIndex inReportLength )
{
unsigned char leftTrigger, rightTrigger;
short rightAxisX, rightAxisY;
short leftAxisX, leftAxisY;
unsigned short buttonFlags;
// Copy bytes to their respective variables (incase they are not aligned correctly)
memcpy( &buttonFlags, &inReport[2], sizeof( unsigned short ) );
memcpy( &leftTrigger, &inReport[4], sizeof( unsigned char ) );
memcpy( &rightTrigger, &inReport[5], sizeof( unsigned char ) );
memcpy( &leftAxisX, &inReport[6], sizeof( short ) );
memcpy( &leftAxisY, &inReport[8], sizeof( short ) );
memcpy( &rightAxisX, &inReport[10], sizeof( short ) );
memcpy( &rightAxisY, &inReport[12], sizeof( short ) );
// Output pad stats
printf( "Buttons %u\n", buttonFlags );
printf( "LTrigger: %u\n", leftTrigger );
printf( "RTrigger: %u\n", rightTrigger );
printf( "LAxis: %d, %d\n", leftAxisX, leftAxisY );
printf( "RAxis: %d, %d\n", rightAxisX, rightAxisY );
printf("\n");
}
I don’t know what the 2048 button flag is, it is possibly the synchronization button that is on the wireless controller. So here is a list of all the other button flags and their bit number.
//
//
enum XBox360_ButtonFlags
{
XBOX360PAD_DPAD_UP = 1,
XBOX360PAD_DPAD_DOWN = 2,
XBOX360PAD_DPAD_LEFT = 4,
XBOX360PAD_DPAD_RIGHT = 8,
XBOX360PAD_START = 16,
XBOX360PAD_BACK = 32,
XBOX360PAD_LAXIS = 64,
XBOX360PAD_RAXIS = 128,
XBOX360PAD_LB = 256,
XBOX360PAD_RB = 512,
XBOX360PAD_XBOX = 1024,
XBOX360PAD_A = 4096,
XBOX360PAD_B = 8192,
XBOX360PAD_X = 16384,
XBOX360PAD_Y = 32768,
};
There is still a lot more to cover, so I’ll probably make a follow up entry tomorrow or the day after for sending messages to the pad to make it vibrate, and how to clean up after you’re finished properly. As well as post an example application.
I also found a few new variables to add to my list of debugging preprocessor. I’ll explain on the next page since this one is getting rather long.