Working With Legacy HID Class Device Interfaces
This chapter provides step-by-step instructions, including listings of sample code, for accessing a HID class device on older versions of OS X. If you are developing new code for OS X v10.5 or later, you should use the APIs described in Accessing a HID Device..
These examples assume that you have already obtained an iterator for a list of devices matching certain properties (for example, by using the MyFindHIDDevices
function, Listing 3-3).
Now that you have a device iterator, you can use it to examine each device and create a device interface for it. The functions in this chapter demonstrate this process by first displaying the properties and then creating and testing a device interface for each device in turn.
This chapter contains the following sections:
For code to help you locate HID class devices, see Legacy HID Access Overview.
Printing the Properties of a HID Class Device
Since the properties of a HID class device often contain nested element dictionaries, the functions that access the elements must do so in a recursive manner. In the sample code, the MyShowHIDProperties
function uses CFShow to print a dictionary.
Listing 4-1 A function that shows all properties for a HID class device
static void MyShowHIDProperties(io_registry_entry_t hidDevice) |
{ |
kern_return_t result; |
CFMutableDictionaryRef properties = 0; |
char path[512]; |
result = IORegistryEntryGetPath(hidDevice, kIOServicePlane, path); |
if (result == KERN_SUCCESS) |
printf("[ %s ]", path); |
//Create a CF dictionary representation of the I/O |
//Registry entry's properties |
result = IORegistryEntryCreateCFProperties(hidDevice, &properties, |
kCFAllocatorDefault, kNilOptions); |
if ((result == KERN_SUCCESS) && properties) |
{ |
CFShow(properties); |
/* Some common properties of interest include: |
kIOHIDTransportKey, kIOHIDVendorIDKey, |
kIOHIDProductIDKey, kIOHIDVersionNumberKey, |
kIOHIDManufacturerKey, kIOHIDProductKey, |
kIOHIDSerialNumberKey, kIOHIDLocationIDKey, |
kIOHIDPrimaryUsageKey, kIOHIDPrimaryUsagePageKey, |
and kIOHIDElementKey. |
*/ |
//Release the properties dictionary |
CFRelease(properties); |
} |
printf("\n\n"); |
} |
MyShowHIDProperties
takes the HID class device found in the I/O Registry and uses IORegistryEntryCreateCFProperties
to create a CF dictionary representation of the device’s properties. It then calls CFShow to format the information and print it to the screen.
Creating a HID Class Device Interface
A device interface provides functions your application or other code running on OS X can use to access a device. The MyCreateHIDDeviceInterface
function, shown in Listing 4-2, creates and returns a HID class device interface based on a passed object from the I/O Registry. It also demonstrates how to obtain the class name of the passed object by calling the I/O Kit function IOObjectGetClass
.
To create a HID class device interface, the MyCreateHIDDeviceInterface
function performs the following steps:
It obtains an interface of type
IOCFPlugInInterface
for the HID class device represented by the passedhidDevice
object.To obtain this interface, it calls the
IOCreatePlugInInterfaceForService
function, passing the valuekIOHIDDeviceUserClientTypeID
for the plug-in type parameter and the valuekIOCFPlugInInterfaceID
for the interface type parameter.kIOHIDDeviceUserClientTypeID
is defined inIOHIDLib.h
andkIOCFPlugInInterfaceID
is defined inIOCFPlugIn.h
(located inIOKit.framework
).It obtains a HID class device interface by calling the
QueryInterface
method of theIOCFPlugInInterface
object. To specify a HID class device interface, it uses the following term:CFUUIDGetUUIDBytes(
kIOHIDDeviceInterfaceID
)
This term produces a UUID (universally unique identifier) for the device interface. UUIDs are described in the Core Foundation developer documentation, available in the Reference Library > Core Foundation section of the developer documentation website.
kIOHIDDeviceInterfaceID
is defined inIOHIDLib.h
.It releases the
IOCFPlugInInterface
object and returns the HID class device interface in thehidDeviceInterface
parameter.
Listing 4-2 A function that creates and returns a HID class device interface based on a passed object from the I/O Registry
static void MyCreateHIDDeviceInterface(io_object_t hidDevice, |
IOHIDDeviceInterface ***hidDeviceInterface) |
{ |
io_name_t className; |
IOCFPlugInInterface **plugInInterface = NULL; |
HRESULT plugInResult = S_OK; |
SInt32 score = 0; |
IOReturn ioReturnValue = kIOReturnSuccess; |
ioReturnValue = IOObjectGetClass(hidDevice, className); |
print_errmsg_if_io_err(ioReturnValue, "Failed to get class name."); |
printf("Found device type %s\n", className); |
ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice, |
kIOHIDDeviceUserClientTypeID, |
kIOCFPlugInInterfaceID, |
&plugInInterface, |
&score); |
if (ioReturnValue == kIOReturnSuccess) |
{ |
//Call a method of the intermediate plug-in to create the device |
//interface |
plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, |
CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), |
(LPVOID *) hidDeviceInterface); |
print_errmsg_if_err(plugInResult != S_OK, "Couldn't create HID class device interface"); |
(*plugInInterface)->Release(plugInInterface); |
} |
} |
Obtaining HID Cookies
The following function, getHIDCookies
(shown in Listing 4-3), places certain cookies associated with the HID class device’s x axis, button 1, button 2, and button 3 into a data structure so the queue handling functions can add these elements to a queue.
Listing 4-3 A function that stores selected cookies in global variables for later use
typedef struct cookie_struct |
{ |
IOHIDElementCookie gXAxisCookie; |
IOHIDElementCookie gButton1Cookie; |
IOHIDElementCookie gButton2Cookie; |
IOHIDElementCookie gButton3Cookie; |
} *cookie_struct_t; |
cookie_struct_t getHIDCookies(IOHIDDeviceInterface122 **handle) |
{ |
cookie_struct_t cookies = memset(malloc(sizeof(*cookies)), 0, |
sizeof(*cookies)); |
CFTypeRef object; |
long number; |
IOHIDElementCookie cookie; |
long usage; |
long usagePage; |
CFArrayRef elements; // |
CFDictionaryRef element; |
IOReturn success; |
if (!handle || !(*handle)) return cookies; |
// Copy all elements, since we're grabbing most of the elements |
// for this device anyway, and thus, it's faster to iterate them |
// ourselves. When grabbing only one or two elements, a matching |
// dictionary should be passed in here instead of NULL. |
success = (*handle)->copyMatchingElements(handle, NULL, &elements); |
printf("LOOKING FOR ELEMENTS.\n"); |
if (success == kIOReturnSuccess) { |
CFIndex i; |
printf("ITERATING...\n"); |
for (i=0; i<CFArrayGetCount(elements); i++) |
{ |
element = CFArrayGetValueAtIndex(elements, i); |
// printf("GOT ELEMENT.\n"); |
//Get cookie |
object = (CFDictionaryGetValue(element, |
CFSTR(kIOHIDElementCookieKey))); |
if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) |
continue; |
if(!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, |
&number)) |
continue; |
cookie = (IOHIDElementCookie) number; |
//Get usage |
object = CFDictionaryGetValue(element, CFSTR(kIOHIDElementUsageKey)); |
if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) |
continue; |
if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, |
&number)) |
continue; |
usage = number; |
//Get usage page |
object = CFDictionaryGetValue(element, |
CFSTR(kIOHIDElementUsagePageKey)); |
if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) |
continue; |
if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, |
&number)) |
continue; |
usagePage = number; |
//Check for x axis |
if (usage == 0x30 && usagePage == 0x01) |
cookies->gXAxisCookie = cookie; |
//Check for buttons |
else if (usage == 0x01 && usagePage == 0x09) |
cookies->gButton1Cookie = cookie; |
else if (usage == 0x02 && usagePage == 0x09) |
cookies->gButton2Cookie = cookie; |
else if (usage == 0x03 && usagePage == 0x09) |
cookies->gButton3Cookie = cookie; |
} |
printf("DONE.\n"); |
} else { |
printf("copyMatchingElements failed with error %d\n", success); |
} |
return cookies; |
} |
Performing Operations on a HID Class Device
Once you have found a HID class device in the I/O Registry and created a device interface for it, you can perform operations such as:
opening the device
getting element values
creating and manipulating queues
closing the device
The sample code in this section performs these types of operations through the test function MyTestHIDDeviceInterface
, shown in Listing 4-4. This function is passed a HID class device interface and it first calls the device interface function open
to open the device. Next, it calls MyTestQueues
, shown in Listing 4-5, to create and manipulate a queue. Then, it calls MyTestHIDInterface
to get various element values and print them. Finally, it closes the device by calling the device interface function close
.
Listing 4-4 A function that performs testing on a passed HID class device interface
static void MyTestHIDDeviceInterface(IOHIDDeviceInterface **hidDeviceInterface, cookie_struct_t cookies) |
{ |
IOReturn ioReturnValue = kIOReturnSuccess; |
//open the device |
ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, 0); |
printf("Open result = %d\n", ioReturnValue); |
//test queue interface |
MyTestQueues(hidDeviceInterface, cookies); |
//test the interface |
MyTestHIDInterface(hidDeviceInterface, cookies); |
//close the device |
if (ioReturnValue == KERN_SUCCESS) |
ioReturnValue = (*hidDeviceInterface)->close(hidDeviceInterface); |
//release the interface |
(*hidDeviceInterface)->Release(hidDeviceInterface); |
} |
The MyTestQueues
function (shown in Listing 4-5) first calls the HID device interface function allocQueue
to allocate a queue. Next, it uses the queue handling functions addElement
, hasElement
, and removeElement
to add elements to the queue, check to see if an element is in the queue, and remove an element, respectively. Finally, MyTestQueues
simulates a game loop by calling the start
queue function to start data delivery to the queue and then repeatedly calling the getNextEvent
queue function to retrieve the values for elements in the queue. All queue handling functions for HID class devices are defined in IOHIDLib.h
.
Listing 4-5 A function to test the HID class device interface queue handling functions
static void MyTestQueues(IOHIDDeviceInterface **hidDeviceInterface, cookie_struct_t cookies) |
{ |
HRESULT result; |
IOHIDQueueInterface ** queue; |
Boolean hasElement; |
long index; |
IOHIDEventStruct event; |
queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); |
if (queue) |
{ |
printf("Queue allocated: %lx\n", (long) queue); |
//create the queue |
result = (*queue)->create(queue, 0, 8); |
/* depth (8): maximum number of elements in queue before oldest |
elements in queue begin to be lost. |
*/ |
printf("Queue create result: %lx\n", result); |
//add elements to the queue |
result = (*queue)->addElement(queue, cookies->gXAxisCookie, 0); |
printf("Queue added x axis result: %lx\n", result); |
result = (*queue)->addElement(queue, cookies->gButton1Cookie, 0); |
printf("Queue added button 1 result: %lx\n", result); |
result = (*queue)->addElement(queue, cookies->gButton2Cookie, 0); |
printf("Queue added button 2 result: %lx\n", result); |
result = (*queue)->addElement(queue, cookies->gButton3Cookie, 0); |
printf("Queue added button 3 result: %lx\n", result); |
//check to see if button 3 is in queue |
hasElement = (*queue)->hasElement(queue,cookies->gButton3Cookie); |
printf("Queue has button 3 result: %s\n", hasElement ? "true" : |
"false"); |
//remove button 3 from queue |
result = (*queue)->removeElement(queue, cookies->gButton3Cookie); |
printf("Queue remove button 3 result: %lx\n",result); |
//start data delivery to queue |
result = (*queue)->start(queue); |
printf("Queue start result: %lx\n", result); |
//check queue a few times to see accumulated events |
sleep(1); |
printf("Checking queue\n"); |
for (index = 0; index < 10; index++) |
{ |
AbsoluteTime zeroTime = {0,0}; |
result = (*queue)->getNextEvent(queue, &event, zeroTime, 0); |
if (result) |
printf("Queue getNextEvent result: %lx\n", result); |
else |
printf("Queue: event:[%lx] %ld\n", |
(unsigned long) event.elementCookie, |
event.value); |
sleep(1); |
} |
//stop data delivery to queue |
result = (*queue)->stop(queue); |
printf("Queue stop result: %lx\n", result); |
//dispose of queue |
result = (*queue)->dispose(queue); |
printf("Queue dispose result: %lx\n", result); |
//release the queue we allocated |
(*queue)->Release(queue); |
} |
} |
The MyTestHIDInterface
function uses the passed-in HID device interface to call the device interface getElement
function to get the element values associated with the cookies stored in the global variables gXAxisCookie
, gButton1Cookie
, gButton2Cookie
, and gButton3Cookie
by getHIDCookies
(shown in Listing 4-3). The sample code simulates a game loop by repeatedly calling
getElementValue
in a for
loop.
Listing 4-6 A function that uses HID class device interface functions to get and display selected element values over time
static void MyTestHIDInterface(IOHIDDeviceInterface ** hidDeviceInterface, cookie_struct_t cookies) |
{ |
HRESULT result; |
IOHIDEventStruct hidEvent; |
long index; |
printf("X Axis (%lx), Button 1 (%lx), Button 2 (%lx), Button 3 ($lx)\n", |
(long) cookies->gXAxisCookie, (long) cookies->gButton1Cookie, |
(long) cookies->gButton2Cookie, (long) cookies->gButton3Cookie); |
for (index = 0; index < 10; index++) |
{ |
long xAxis, button1, button2, button3; |
//Get x axis |
result = (*hidDeviceInterface)->getElementValue(hidDeviceInterface, |
cookies->gXAxisCookie, &hidEvent); |
if (result) |
printf("getElementValue error = %lx", result); |
xAxis = hidEvent.value; |
//Get button 1 |
result = (*hidDeviceInterface)->getElementValue(hidDeviceInterface, |
cookies->gButton1Cookie, &hidEvent); |
if (result) |
printf("getElementValue error = %lx", result); |
button1 = hidEvent.value; |
//Get button 2 |
result = (*hidDeviceInterface)->getElementValue(hidDeviceInterface, |
cookies->gButton2Cookie, &hidEvent); |
if (result) |
printf("getElementValue error = %lx", result); |
button2 = hidEvent.value; |
//Get button 3 |
result = (*hidDeviceInterface)->getElementValue(hidDeviceInterface, |
cookies->gButton3Cookie, &hidEvent); |
if (result) |
printf("getElementValue error = %lx", result); |
button3 = hidEvent.value; |
//Print values |
printf("%ld %s%s%s\n", xAxis, button1 ? "button1 " : "", |
button2 ? "button2 " : "", button3 ? "button3 " : ""); |
sleep(1); |
} |
} |
Using Queue Callbacks
There are two basic ways of using queues when interacting with HID devices: polling and callbacks. In Listing 4-5, queues are accessed in a polled fashion. The example project “hidexample” uses this mechanism.
For performance reasons (and for programming convenience), it often makes more sense to use queue callbacks. In this usage, a function in your program is called whenever the queue becomes non-empty.
The code snippet is relatively straightforward. Essentially, you allocate a private data structure that contains a reference to the queue, create an asynchronous event source for the queue, add a queue callback handler, and finally, add the newly-created queue event source to your run loop.
The queue callback is passed two void *
parameters, callbackRefcon
and callbackTarget
, both of which can contain pointers to arbitrary data. With this design, the same callback function can operate on multiple queues, feeding data to multiple class instances, simply by passing different queue and class pointers to the setEventCallout
function.
In Listing 4-7, the callbackTarget
parameter will be passed NULL
, and the callbackRefcon
parameter will be passed a dynamically-allocated object containining the hidQueueInterface
pointer (just for grins).
Listing 4-7 Adding a Queue Callback
bool addQueueCallbacks(IOHIDQueueInterface **hidQueueInterface) |
{ |
IOReturn ret; |
CFRunLoopSourceRef eventSource; |
/* We could use any data structure here. This data structure |
will be passed to the callback, and should probably |
include some information about the queue, assuming your |
program deals with more than one. */ |
IOHIDQueueInterface ***myPrivateData = malloc(sizeof(*myPrivateData)); |
*myPrivateData = hidQueueInterface; |
// In the calling function, we did something like: |
// hidQueueInterface = (*hidDeviceInterface)-> |
// allocQueue(hidDeviceInterface); |
// (*hidQueueInterface)->create(hidQueueInterface, 0, 8); |
ret = (*hidQueueInterface)-> |
createAsyncEventSource(hidQueueInterface, |
&eventSource); |
if ( ret != kIOReturnSuccess ) |
return false; |
ret = (*hidQueueInterface)-> |
setEventCallout(hidQueueInterface, |
QueueCallbackFunction, NULL, myPrivateData); |
if ( ret != kIOReturnSuccess ) |
return false; |
CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, |
kCFRunLoopDefaultMode); |
return true; |
} |
The final example in this section is taken from the “HidTestTool” example. This snippet shows how to handle a queue callback. This code is structurally somewhat different in that a data structure is passed as the refcon
argument. This allows it to pass in both information about the queue and information about the elements supported by a given HID device.
Listing 4-8 A Basic Queue Callback Function
static void QueueCallbackFunction( |
void * target, |
IOReturn result, |
void * refcon, |
void * sender) |
{ |
HIDDataRef hidDataRef = (HIDDataRef)refcon; |
AbsoluteTime zeroTime = {0,0}; |
CFNumberRef number = NULL; |
CFMutableDataRef element = NULL; |
HIDElementRef tempHIDElement = NULL;//(HIDElementRef)refcon; |
IOHIDEventStruct event; |
bool change; |
bool stateChange = false; |
if ( !hidDataRef || ( sender != hidDataRef->hidQueueInterface)) |
return; |
while (result == kIOReturnSuccess) |
{ |
result = (*hidDataRef->hidQueueInterface)->getNextEvent( |
hidDataRef->hidQueueInterface, |
&event, |
zeroTime, |
0); |
if ( result != kIOReturnSuccess ) |
continue; |
// Only intersted in 32 values right now |
if ((event.longValueSize != 0) && (event.longValue != NULL)) |
{ |
free(event.longValue); |
continue; |
} |
number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, |
&event.elementCookie); |
if ( !number ) continue; |
element = (CFMutableDataRef)CFDictionaryGetValue(hidDataRef-> |
hidElementDictionary, number); |
CFRelease(number); |
if ( !element || |
!(tempHIDElement = (HIDElement *)CFDataGetMutableBytePtr(element))) |
continue; |
change = (tempHIDElement->currentValue != event.value); |
tempHIDElement->currentValue = event.value; |
} |
} |
Copyright © 2001, 2009 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2009-10-19