UHIDTest.c

/*
    File:       UHIDTest.c
 
    Contains:   xxx put contents here xxx
 
    Version:    xxx put version here xxx
 
    Copyright:  © 1999 by Apple Computer, Inc., all rights reserved.
 
    File Ownership:
 
        DRI:                xxx put dri here xxx
 
        Other Contact:      xxx put other contact here xxx
 
        Technology:         xxx put technology here xxx
 
    Writers:
 
        (BWS)   Brent Schorsch
 
    Change History (most recent first):
 
       <SP6>     3/20/99    BWS     Update to lastest HIDOpenReportDescriptor API
       <SP5>      3/7/99    BWS     Add Simulate ISp enumeration - similar code to ISp driver
       <SP4>      3/7/99    BWS     Print out caps usage page and usage. Print all usages in hex
       <SP3>      3/7/99    BWS     Implemented get capabilities and print parsed data
       <SP2>      3/5/99    BWS     Added HIDLib calls
*/
 
#include <MacTypes.h>
 
#include <SIOUX.h>
#include <LowMem.h>
#include <USB.h>
#include <InputSprocket.h>
#include "HID.h"
//#include "UniversalHIDModule.h"
 
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
//#include "HIDPriv.h"
 
// extern protos
extern void PrintHIDReport(UInt8 * reportDesc, UInt32 length);
 
 
// globals
 
// quit state
Boolean                         gShouldQuit = false;
 
// steps of process
SInt32                          gStep;
char                            gStepId[255];
 
// 'safe' (delayed) printf
enum { kPrintfBufferSize        = 1024 * 32 };
 
UInt32                          gMaxPacketSize;
char                            gPrintfBuffer1[kPrintfBufferSize + 256];
char                            gPrintfBuffer2[kPrintfBufferSize + 256];
char *                          gInsertPrintfBuffer;
char *                          gFreePrintfBuffer;
UInt32                          gInsertIndex = 0;
 
enum
{
    kPrint_AsHex            = 1,
    kPrint_AsBinary,
    kPrint_ParseButtons,
    kPrint_ParseBoth,
    kPrint_SimulateISp
};
 
UInt32                          gPrint_AsType;
 
// HID stuff
 
// these constants are from the USB HID Usage Table spec
enum
{
    kHIDPage_Generic        = 1,
    kHIDUsage_Joystick      = 0x04,
    kHIDUsage_Gamepad       = 0x05,
    kHIDUsage_X             = 0x30,
    kHIDUsage_Y             = 0x31,
    kHIDUsage_Z             = 0x32,
    kHIDUsage_Rx            = 0x33,
    kHIDUsage_Ry            = 0x34,
    kHIDUsage_Rz            = 0x35,
    kHIDUsage_Slider        = 0x36,
    kHIDUsage_Dial          = 0x37,
    kHIDUsage_Wheel         = 0x38,
    kHIDUsage_HatSwitch     = 0x39,
    
    kHIDPage_Simulation     = 2,
    kHIDUsage_Rudder        = 0xBA,
    kHIDUsage_Throttle      = 0xBB,
 
    kHIDPage_Button         = 9
};
 
// these constants are used to describe specific values for an element (a usbElementKey)
enum
{
    // it is important that all buttons come first and start at 1
    kUSB_button1_element = 1,
    kUSB_button2_element,
    kUSB_button3_element,
    kUSB_button4_element,
    kUSB_button5_element,
    kUSB_button6_element,
    kUSB_button7_element,
    kUSB_button8_element,
    kUSB_button9_element,
    kUSB_button10_element,
    kUSB_button11_element,
    kUSB_button12_element,
    kUSB_button13_element,
    kUSB_button14_element,
    kUSB_button15_element,
    kUSB_button16_element,
    kUSB_trigger_element,
    kUSB_buttonStart_element,
    kUSB_buttonSelect_element,
    kUSB_povhat_element,
    kUSB_povhat4_element,
    kUSB_dpad_element,
    kUSB_dpad4_element,
    kUSB_xaxis_element,
    kUSB_yaxis_element,
    kUSB_throttle_element,
    kUSB_rudder_element,
    kUSB_trim_element,
    kUSB_gas_element,
    kUSB_brake_element,
    kUSB_axis_element
};
 
enum
{
    kStrListResource_DefaultNames = 128
};
 
enum
{
    kStrList_DefaultNames_Trigger = 1,
    kStrList_DefaultNames_LeftTrigger,
    kStrList_DefaultNames_RightTrigger,
    kStrList_DefaultNames_LeftIndexTop,
    kStrList_DefaultNames_LeftIndexBottom,
    kStrList_DefaultNames_RightIndexTop,
    kStrList_DefaultNames_RightIndexBottom,
    kStrList_DefaultNames_ThumbLeftButton,
    kStrList_DefaultNames_ThumbCenterButton,
    kStrList_DefaultNames_ThumbRightButton,
    kStrList_DefaultNames_ThumbTopButton,
    kStrList_DefaultNames_ThumbBottomButton,
    kStrList_DefaultNames_AButton,
    kStrList_DefaultNames_BButton,
    kStrList_DefaultNames_CButton,
    kStrList_DefaultNames_DButton,
    kStrList_DefaultNames_EButton,
    kStrList_DefaultNames_FButton,
    kStrList_DefaultNames_XButton,
    kStrList_DefaultNames_YButton,
    kStrList_DefaultNames_ZButton,
    kStrList_DefaultNames_MButton,
    kStrList_DefaultNames_TAButton,
    kStrList_DefaultNames_TBButton,
    kStrList_DefaultNames_F1Button,
    kStrList_DefaultNames_F2Button,
    kStrList_DefaultNames_F3Button,
    kStrList_DefaultNames_F4Button,
    kStrList_DefaultNames_F5Button,
    kStrList_DefaultNames_F6Button,
    kStrList_DefaultNames_F7Button,
    kStrList_DefaultNames_F8Button,
    kStrList_DefaultNames_YellowButton,
    kStrList_DefaultNames_RedButton,
    kStrList_DefaultNames_GreenButton,
    kStrList_DefaultNames_BlueButton,
    kStrList_DefaultNames_StartButton,
    kStrList_DefaultNames_SelectButton,
    kStrList_DefaultNames_ShiftButton,
    kStrList_DefaultNames_LeftShiftButton,
    kStrList_DefaultNames_RightShiftButton,
    kStrList_DefaultNames_SensorButton,
    kStrList_DefaultNames_Button1,
    kStrList_DefaultNames_Button2,
    kStrList_DefaultNames_Button3,
    kStrList_DefaultNames_Button4,
    kStrList_DefaultNames_Button5,
    kStrList_DefaultNames_Button6,
    kStrList_DefaultNames_Button7,
    kStrList_DefaultNames_Button8,
    kStrList_DefaultNames_Button9,
    kStrList_DefaultNames_Button10,
    kStrList_DefaultNames_Button11,
    kStrList_DefaultNames_Button12,
    kStrList_DefaultNames_Button13,
    kStrList_DefaultNames_Button14,
    kStrList_DefaultNames_Button15,
    kStrList_DefaultNames_Button16,
    kStrList_DefaultNames_Button,
    kStrList_DefaultNames_POVHat,
    kStrList_DefaultNames_DPad,
    kStrList_DefaultNames_XAxis,
    kStrList_DefaultNames_YAxis,
    kStrList_DefaultNames_XTilt,
    kStrList_DefaultNames_YTilt,
    kStrList_DefaultNames_Twist,
    kStrList_DefaultNames_Throttle,
    kStrList_DefaultNames_Trim,
    kStrList_DefaultNames_Rudder,
    kStrList_DefaultNames_Axis,
    kStrList_DefaultNames_Wheel,
    kStrList_DefaultNames_Gas,
    kStrList_DefaultNames_Brake,
    kStrList_DefaultNames_Error
};
 
enum
{
    kButtonElement  = 1,
    kValueElement   = 2
};
 
struct  HIDElement
{
    UInt32      type;
    UInt32      capsIndex;
    HIDUsage    usagePage;
    HIDUsage    usage;
    UInt32      key;
    SInt32      min;
    SInt32      max;
    SInt32      stringIndex;
};
typedef struct HIDElement HIDElement;
 
HIDCaps             gHIDCaps;
HIDButtonCaps *     gHIDButtonCaps;
HIDValueCaps *      gHIDValueCaps;
HIDElement *        gHIDElements;
UInt32              gHIDElementCount = 0;
 
enum { kMaxReportDescSize       = 1024 };
UInt8                           gReportDesc[kMaxReportDescSize];
UInt32                          gReportDescLength;
 
HIDPreparsedDataRef             gPreparsedDataRef;
HIDPreparsedData                gPreparsedData;
UInt32                          gMaxUsageListLength = 0;
HIDUsageAndPage *               gPreviousDownButtons = nil;
HIDUsageAndPage *               gCurrentDownButtons = nil;
 
// lists of dispatch tables
enum { kMaxDevices      = 128 };
 
UHIDModuleDispatchTablePtr      gUHID_dispatchTables[kMaxDevices];
int                             gUHID_count = 0;
UHIDModuleDispatchTablePtr      gCurrentUHID_dispatchTable;
UHIDModuleConnectionID          gCurrentUHIDModuleConnectionID;
 
USBHIDModuleDispatchTablePtr    gMouse_dispatchTables[kMaxDevices];
int                             gMouse_count = 0;
 
// macros
 
#define VERIFY(x)   if (x) { FailCode(x); return; }
 
#define FAILMSG(x) { FailMsg(x); return; }
#define FAILCODE(x) { FailCode(x); return; }
 
#define FFAILMSG(x) { FailMsg(x); return false; }
#define FFAILCODE(x) { FailCode(x); return false; }
 
// protos
 
void    ShowFourByte(OSType fourByte);
 
void    ExitProc (void);
void    DoHIDTest (void);
 
void    GetButtonCapabilities (void);
void    GetValueCapabilities (void);
void    PrintButtonCapabilities (HIDCaps hidCaps, HIDButtonCaps * hidButtonCaps);
void    PrintValueCapabilities (HIDCaps hidCaps, HIDValueCaps * hidValueCaps);
 
void    ClaimAndInstallHandler (void);
void    RemoveHandlerAndRelease (void);
void    myUniversalInterruptProc(void * theData, UInt32 refcon);
 
 
OSStatus    HidP_PrintAllInputValues
          (UInt32                       iCollection,
          HIDPreparsedDataPtr       ptPreparsedData,
           char                      *psReport,
           int                        iReportLength);
 
 
int safeprintf(const char *format, ...);
 
void PrintPreparsedData (void);
void PrintCollectionItems (UInt32 firstCollection, UInt32 collectionCount, char * indent);
void PrintReportItems (UInt32 firstReportItem, UInt32 reportItemCount, char * indent);
void PrintUsageItems (UInt32 firstUsageItem, UInt32 usageItemCount, char * indent);
 
void        SimulateISpEnumeration (void);
OSStatus    PrintHIDElements (void);
OSStatus    BuildHIDElements (void);
UInt32      CountHIDElements(void);
OSStatus    InitializeHIDElement(HIDElement * hidElement);
SInt32      ParseElementValue (UInt32 inElementIndex, Ptr inBuffer);
 
// code
 
static Boolean Progress(UInt32 inItr, UInt32 inTotal, UInt32 inNumReports, char *string)
{
    UInt32 everyNTimes = inTotal / inNumReports;
    
    if (everyNTimes == 0) { everyNTimes = 1; }
    
    if (inItr == 0) { return false; }
    
    if ((inItr % everyNTimes) == 0)
    {
        UInt32 percent = (inItr * 100) / inTotal;
        sprintf(string, "%d%% [%d of %d]", percent, inItr, inTotal);
        return true;
    }
    
    return false;
}
 
static void InitStep(char *msg)
{
    gStep = 0;
    sprintf(gStepId, "%3d");
    
    if (msg == nil)
    {
        printf("step %s\n", gStepId);
    }
    else
    {
        printf("step %s %s\n",gStepId, msg);
    }
}
 
static void NextStep(char *msg)
{
    long this_app_memory;
    long this_sys_memory;
    static Boolean first_time = true;
    static long last_app_memory;
    static long last_sys_memory;
 
    gStep++;
    sprintf(gStepId, "%3d", gStep);
 
    this_app_memory = FreeMem();
    this_sys_memory = FreeMemSys();
 
    if (first_time) 
    {
        first_time = false;
    }
    else
    {
        long delta_app_memory = last_app_memory - this_app_memory;
        long delta_sys_memory = last_sys_memory - this_sys_memory;
        
        printf("step %s ending app mem = %d sys mem = %d\n", gStepId, this_app_memory, this_sys_memory);
        printf("step %s delta app mem = %d sys mem = %d\n", gStepId, delta_app_memory, delta_sys_memory);
    }
 
    if (msg == nil)
    {
        printf("step %s\n", gStepId);
    }
    else
    {
        printf("step %s %s\n",gStepId, msg);
    }
 
    printf("step %s starting app mem = %d sys mem = %d\n", gStepId, this_app_memory, this_sys_memory);
 
    last_app_memory = this_app_memory;
    last_sys_memory = this_sys_memory;
}
 
static void FailMsg(char *failure)
{
    printf("step %s FAILED reason = %s\n", gStepId, failure);
}
 
static void FailCode(OSStatus errorCode)
{
    printf("step %s FAILED errorCode = %d\n", gStepId, errorCode);
}
 
static void StatusMsg(char *msg)
{
    printf("step %s status msg = %s\n",gStepId, msg);
}
 
void ShowFourByte(OSType fourByte)
{
    putchar(((fourByte & 0xff000000) >> 24));
    putchar(((fourByte & 0x00ff0000) >> 16));
    putchar(((fourByte & 0x0000ff00) >> 8));
    putchar(((fourByte & 0x000000ff) >> 00));
}
 
void main(void)
{
    OSErr                           status = noErr;
    CFragConnectionID               usbConnID;
    USBDeviceRef                    usbDeviceRef;
    CFragSymbolClass                symClass;
    UHIDModuleDispatchTablePtr      uHID_dispatchTable;
//  USBHIDModuleDispatchTablePtr    mouse_dispatchTable;
    UInt16                          vendor;
    UInt16                          product;
    THz                             currentZone;
    
        
#if defined(__MWERKS__)
    // tell SIOUX to shut up
    SIOUXSettings.autocloseonquit = false;
    SIOUXSettings.asktosaveonclose = false;
    SIOUXSettings.columns = 120;
    SIOUXSettings.rows = 70;
#endif
    
    printf("starting up...\n");
    
    printf("searching for universal HID devices\n");
    
    usbDeviceRef = 0;
    while (status == noErr && gUHID_count < kMaxDevices)
    {
        printf ("searching for next device...\n");
        
        status = USBGetNextDeviceByClass (&usbDeviceRef, &usbConnID, kUSBHIDClass, kUSBCompositeSubClass, kUSBNoInterfaceProtocol);
        if (status)
        {
            if (status != kUSBNotFound && status != fnfErr)
                FailCode (status);
            continue;
        }
        
        printf ("candidate found (connID: %00000008x), searching for symbol...\n", usbConnID);
        
        // need to be in the system zone when we search for the symbol
        currentZone = GetZone ();
        SetZone (SystemZone ());
        status = FindSymbol (usbConnID, "\pTheUHIDModuleDispatchTable", (Ptr *)&uHID_dispatchTable, &symClass);
        SetZone (currentZone);
        
        // if we failed to find it, go on to the next device    
        if (status)
        {
            FailCode (status);
            status = noErr; 
            continue;
        }
        
        printf("symbol found, checking versions...\n");
        
        // version checking
        if (uHID_dispatchTable->dispatchTableCurrentVersion < kOldestCompatableDispatchTableVersion)
        {
            printf ("         DispatchTable current version (%d) too old (%d)\n", 
                uHID_dispatchTable->dispatchTableCurrentVersion, kOldestCompatableDispatchTableVersion);
            continue;
        }
        if (uHID_dispatchTable->dispatchTableOldestVersion > kCurrentDispatchTableVersion)
        {
            printf ("         DispatchTable oldest version (%d) too new (%d)\n", 
                uHID_dispatchTable->dispatchTableOldestVersion, kCurrentDispatchTableVersion);
            continue;
        }
        
        gUHID_dispatchTables[gUHID_count++] = uHID_dispatchTable;
    
        (UHIDInstallInterruptProcPtr)(*(uHID_dispatchTable->pUHIDGetDeviceInfo))(kUHIDGetVenderID, &vendor);
        (UHIDInstallInterruptProcPtr)(*(uHID_dispatchTable->pUHIDGetDeviceInfo))(kUHIDGetProductID, &product);
        printf("version check success, valid device (Vender:%0004X Product:%0004X).\n", vendor, product);
    }
    
    while (gUHID_count > 0 && !gShouldQuit)
        DoHIDTest();
    
    printf("quitting...\n");
}
 
void    ExitProc (void)
{
    RemoveHandlerAndRelease ();
}
 
void DoHIDTest (void)
{
    OSErr                           status = noErr;
    UHIDModuleDispatchTablePtr      uHID_dispatchTable;
//  USBHIDModuleDispatchTablePtr    mouse_dispatchTable;
    UInt16                          vendor;
    UInt16                          product;
    int                             index;
    char                            string[256];
 
choosedevice:
    // ¥¥¥ memory leak here, but who cares for now... (We never dispose of these)
    gHIDElementCount = 0;
    
    printf ("\nChoose a device:\n");
    
    // choice to quit
    printf ("1. Exit\n");
 
    // print out UHID choices
    index = 0;
    while (index < gUHID_count)
    {
        uHID_dispatchTable = gUHID_dispatchTables[index];
        (UHIDInstallInterruptProcPtr)(*(uHID_dispatchTable->pUHIDGetDeviceInfo))(kUHIDGetVenderID, &vendor);
        (UHIDInstallInterruptProcPtr)(*(uHID_dispatchTable->pUHIDGetDeviceInfo))(kUHIDGetProductID, &product);
 
        printf ("%d. Vender:%0004X Product:%0004X\n", index + 2, vendor, product);
        index++;
    }
 
    // ¥ todo ¥Êprint out mouse choices
 
    // get user choice
    printf ("Selection: ");
    gets (string);
    index = atoi (string);
    printf ("\n");
    
    // handle the quit case
    if (index == 1)
    {
        gShouldQuit = true;
        return;
    }
    
    // setup for this device
    uHID_dispatchTable = gCurrentUHID_dispatchTable = gUHID_dispatchTables[index - 2];
    
    // init the printf double buffer
    gInsertPrintfBuffer = gPrintfBuffer1;
    gFreePrintfBuffer = gPrintfBuffer2;
    gInsertIndex = 0;
    
    // get the max packet size
    gMaxPacketSize = 0;
    (UHIDInstallInterruptProcPtr)(*(uHID_dispatchTable->pUHIDGetDeviceInfo))(kUHIDGetMaxPacketSize, &gMaxPacketSize);
    printf("Max packet size = %d\n", gMaxPacketSize);
    
    printf("Printing the report descriptor\n", status);
 
    // print the HID report descriptor
    gReportDescLength = sizeof (gReportDesc);
    status = (*uHID_dispatchTable->pUHIDGetHIDDescriptor)(kUSBReportDesc, 0, &gReportDescLength, gReportDesc);
    if (status)
        printf("*** Error (%d) on Get Report Descriptor Device\n", status);
    else
        PrintHIDReport(gReportDesc, gReportDescLength);
    
    // setup the HID stuff
    status = HIDOpenReportDescriptor (gReportDesc, gReportDescLength, &gPreparsedDataRef, kHIDFlag_StrictErrorChecking);
    if (status)
        printf("*** Error (%d) on HIDOpenDescriptor (strict error checking) \n", status);
    
    // if error, less error checking on the HID stuff
    if (status)
    {
        status = HIDOpenReportDescriptor (gReportDesc, gReportDescLength, &gPreparsedDataRef, 0);
        if (status)
            printf("*** Error (%d) on HIDOpenDescriptor\n", status);
        else
            printf("HIDOpenDescriptor (lax error checking) succeeded. \n", status);
    }
    
    // allocate our buffers
    gMaxUsageListLength = HIDMaxUsageListLength (kHIDInputReport, 0, gPreparsedDataRef);
    printf("Number of buttons (max usage list length) = %ld\n", gMaxUsageListLength);
    
    gPreviousDownButtons = (HIDUsageAndPage *) NewPtr (gMaxUsageListLength * sizeof (HIDUsageAndPage));
    if (gPreviousDownButtons == nil)
        printf("*** Error (%d) on NewPtr for gPreviousDownButtons\n", MemError());
 
    gCurrentDownButtons = (HIDUsageAndPage *) NewPtr (gMaxUsageListLength * sizeof (HIDUsageAndPage));
    if (gCurrentDownButtons == nil)
        printf("*** Error (%d) on NewPtr for gCurrentDownButtons\n", MemError());
 
    printf("gPreviousDownButtons = %lx, gCurrentDownButtons = %lx\n", gPreviousDownButtons, gCurrentDownButtons);
 
whattoget:
    printf ("1. Exit\n");
    printf ("2. Choose another device\n");
    printf ("3. Get data\n");
    printf ("4. Get button capabilities\n");
    printf ("5. Get value capabilities\n");
    printf ("6. Print preparsed data\n");
    printf ("7. Simulate ISp Enumeration\n");
    
    // get user choice
    printf ("Selection: ");
    gets (string);
    index = atoi (string);
    printf ("\n");
    
    switch (index)
    {
        // exit
        case 1:
            gShouldQuit = true;
            goto close;
        
        case 2:
            goto choosedevice;
        
        case 3:
            break;
        
        case 4:
            GetButtonCapabilities();            
            goto whattoget;
        
        case 5:
            GetValueCapabilities();         
            goto whattoget;
        
        case 6:
            PrintPreparsedData();           
            goto whattoget;
        
        case 7:
            SimulateISpEnumeration();           
            goto whattoget;
        
        default:
            goto whattoget;
    }
 
 
getdata:
 
    printf ("1. Exit\n");
    printf ("2. Previous menu\n");
    printf ("3. Display raw bits as hex\n");
    printf ("4. Display raw bits as binary\n");
    printf ("5. Display parsed buttons\n");
    printf ("6. Display parsed values and buttons\n");
    if (gHIDElementCount > 0)
        printf ("7. Simulate ISp\n");
    
    // get user choice
    printf ("Selection: ");
    gets (string);
    index = atoi (string);
    printf ("\n");
    
    
    // make as binary the default, and use in the unimplemented cases?
    gPrint_AsType = kPrint_AsBinary;
    
    switch (index)
    {
        // exit
        case 1:
            gShouldQuit = true;
            goto close;
        
        case 2:
            goto whattoget;
        
        case 3:
            gPrint_AsType = kPrint_AsHex;
            break;
 
        case 4:
            gPrint_AsType = kPrint_AsBinary;
            break;
 
        case 5:
            gPrint_AsType = kPrint_ParseButtons;
            break;
 
        case 6:
            gPrint_AsType = kPrint_ParseBoth;
            break;
 
        case 7:
            if (gHIDElementCount > 0)
                gPrint_AsType = kPrint_SimulateISp;
            else
                goto getdata;
            break;
        
        default:
            goto getdata;
    }
    
    ClaimAndInstallHandler();
    
    // get data
    
    printf("getting data (press command to end)\n");
 
    while (1)
    {
        KeyMap theKeys;
 
        if (gInsertIndex)   // if we have data in the queue, then printf it
        {
            char * temp = gInsertPrintfBuffer;
            
            gInsertPrintfBuffer = gFreePrintfBuffer;    // must do 1st! - atomic switch
            gInsertIndex = 0;                           // _technically_ bad, could possibly miss some data
            
            gFreePrintfBuffer = temp;
            
            printf(temp);               
            fflush(stdout);
        }
 
 
        GetKeys(theKeys);
 
        if ((theKeys[1] & 0x8000))
        {
            break;
        }
        
        SIOUXHandleOneEvent(nil);
    }
    
 
    RemoveHandlerAndRelease ();
 
    
    if (!gShouldQuit)
        goto getdata;
close:
 
    status = HIDCloseReportDescriptor (gPreparsedDataRef);
    if (status)
        printf("*** Error (%d) on HIDCloseDescriptor\n", status);
 
    DisposePtr ((void *) gPreviousDownButtons);
    gPreviousDownButtons = nil;
    
    DisposePtr ((void *) gCurrentDownButtons);
    gCurrentDownButtons = nil;
    
    printf("finished\n\n");
}
 
void    GetButtonCapabilities(void)
{
    OSStatus err = noErr;
    
    HIDCaps             hidCaps;
    HIDButtonCaps *     hidButtonCaps = nil;
 
    // get the overall capabilities of the device
    if (err == noErr)
        err = HIDGetCaps (gPreparsedDataRef, &hidCaps);
 
    // allocate space for button caps
    if (err == noErr)
    {
        hidButtonCaps = (HIDButtonCaps *) NewPtrSysClear(sizeof(HIDButtonCaps) * hidCaps.numberInputButtonCaps);
        if (hidButtonCaps == nil)
        {
            printf ("\n¥¥ Failed to allocate space for ButtonCaps\n");
            err = memFullErr;
        }
    }
 
    // get the button capabilities
    if (err == noErr)
        err = HIDGetButtonCaps (kHIDInputReport, hidButtonCaps, &hidCaps.numberInputButtonCaps, gPreparsedDataRef);
    
    // print what we found
    if (err == noErr)
        PrintButtonCapabilities (hidCaps, hidButtonCaps);
    
    // dispose the memory
    if (hidButtonCaps != nil)
        DisposePtr((LogicalAddress) hidButtonCaps);
}
 
void    GetValueCapabilities (void)
{
    OSStatus err = noErr;
    
    HIDCaps             hidCaps;
    HIDValueCaps *      hidValueCaps = nil;
 
    // get the overall capabilities of the device
    if (err == noErr)
        err = HIDGetCaps (gPreparsedDataRef, &hidCaps);
 
    // allocate space for value caps
    if (err == noErr)
    {
        hidValueCaps = (HIDValueCaps *) NewPtrSysClear(sizeof(HIDValueCaps) * hidCaps.numberInputValueCaps);
        if (hidValueCaps == nil)
        {
            err = memFullErr;
            printf ("\n¥¥ Failed to allocate space for ValueCaps\n");
        }
    }
 
    // get the value capabilities
    if (err == noErr)
        err = HIDGetValueCaps (kHIDInputReport, hidValueCaps, &hidCaps.numberInputValueCaps, gPreparsedDataRef);
 
    // print what we found
    if (err == noErr)
        PrintValueCapabilities (hidCaps, hidValueCaps);
    
    // dispose the memory
    if (hidValueCaps != nil)
        DisposePtr((LogicalAddress) hidValueCaps);
}
 
void    PrintButtonCapabilities (HIDCaps hidCaps, HIDButtonCaps * hidButtonCaps)
{
    UInt32  index;
    UInt32  reportID = 0;
    
    printf("%d buttons (device usage: %X on page: %d)\n", 
        hidCaps.numberInputButtonCaps, 
        hidCaps.usage, 
        hidCaps.usagePage);
    for (index = 0; index < hidCaps.numberInputButtonCaps; index++)
    {
        if (reportID != hidButtonCaps[index].reportID)
        {
            reportID = hidButtonCaps[index].reportID;
            printf("Report ID: %d\n", reportID);
        }
        
        if (hidButtonCaps[index].isRange)
            printf("%X-%X:%d\n", 
                hidButtonCaps[index].u.range.usageMin, 
                hidButtonCaps[index].u.range.usageMax, 
                hidButtonCaps[index].usagePage);
        else
            printf("%X:%d\n", 
                hidButtonCaps[index].u.notRange.usage, 
                hidButtonCaps[index].usagePage);
    }
 
    printf("\n\n");
}
void    PrintValueCapabilities (HIDCaps hidCaps, HIDValueCaps * hidValueCaps)
{
    UInt32  index;
    UInt32  reportID = 0;
    
    printf("%d values (device usage:%d on page:%d\n", 
        hidCaps.numberInputValueCaps, 
        hidCaps.usage, 
        hidCaps.usagePage);
    for (index = 0; index < hidCaps.numberInputValueCaps; index++)
    {
        if (reportID != hidValueCaps[index].reportID)
        {
            reportID = hidValueCaps[index].reportID;
            printf("Report ID: %d\n", reportID);
        }
        
        if (hidValueCaps[index].isRange)
            printf("%X-%X:%d\t\t(%d<->%d)\t(%d<->%d)\t\t[%d * %d]\t\t¥¥¥¥\n", 
                hidValueCaps[index].u.range.usageMin, 
                hidValueCaps[index].u.range.usageMax, 
                hidValueCaps[index].usagePage,
                hidValueCaps[index].logicalMin,
                hidValueCaps[index].logicalMax,
                hidValueCaps[index].physicalMin,
                hidValueCaps[index].physicalMax,
                hidValueCaps[index].bitSize,
                hidValueCaps[index].reportCount);
        else
            printf("%X:%d\t\t(%d<->%d)\t(%d<->%d)\t\t[%d * %d]\n", 
                hidValueCaps[index].u.notRange.usage, 
                hidValueCaps[index].usagePage,
                hidValueCaps[index].logicalMin,
                hidValueCaps[index].logicalMax,
                hidValueCaps[index].physicalMin,
                hidValueCaps[index].physicalMax,
                hidValueCaps[index].bitSize,
                hidValueCaps[index].reportCount);
 
        if (hidValueCaps[index].reportCount > 1)
            printf("¥¥¥ Report Count > 1\n");
    }
            
    printf("\n\n");
}
 
 
void    ClaimAndInstallHandler (void)
{
    OSErr                           status = noErr;
    UHIDModuleConnectionID          uHIDModuleConnectionID;
 
    if (gCurrentUHID_dispatchTable == nil)
    {
        printf("*** Error: have not specified a device\n");
        return;
    }
    
    if (gCurrentUHIDModuleConnectionID != 0)
    {
        printf("*** Error: Already have device active\n");
        return;
    }
    
    printf("Claiming the device\n");
 
    // claim the device
    status = (OSStatus) ((UHIDClaimDeviceProcPtr)(*(gCurrentUHID_dispatchTable->pUHIDClaimDevice))
                            (&uHIDModuleConnectionID, 0));
    if (status)
    {
        printf("*** Error (%d) on Claim Device\n", status);
        return;
    }
 
    gCurrentUHIDModuleConnectionID = uHIDModuleConnectionID;
    
    // install the handler
 
    printf("Installing the handler\n");
 
    status = (OSStatus) ((UHIDInstallInterruptProcPtr)(*(gCurrentUHID_dispatchTable->pUHIDInstallInterrupt))
                            (uHIDModuleConnectionID, myUniversalInterruptProc, 0));
    if (status)
    {
        printf("*** Error (%d) on Universal Interrupt Handler install\n", status);
        return;
    }
    
 
}
 
void    RemoveHandlerAndRelease (void)
{
    OSErr                           status = noErr;
    
    if (gCurrentUHID_dispatchTable == nil)
    {
        printf("*** Error have not specified a device\n");
        return;
    }
    
    if (gCurrentUHIDModuleConnectionID == 0)
        return;
    
    // remove the handler
    
    status = (OSStatus) (UHIDInstallInterruptProcPtr)(*(gCurrentUHID_dispatchTable->pUHIDInstallInterrupt))
                            (gCurrentUHIDModuleConnectionID, nil, 0);
    if (status)
         printf("*** Error (%d) on Universal Interrupt Handler remove\n", status);
    
    // release the device
    
    status = (OSStatus) ((UHIDReleaseDeviceProcPtr)(*(gCurrentUHID_dispatchTable->pUHIDReleaseDevice))
                            (gCurrentUHIDModuleConnectionID));
    if (status)
         printf("*** Error (%d) on release device\n", status);
    
    gCurrentUHIDModuleConnectionID = 0;
    
}
 
void myUniversalInterruptProc(void * theData, UInt32 /* refcon */)
{
//#pragma unused refcon
 
    UInt16              i;
    UInt8               *pRawData;
    OSErr               status = noErr;
    UInt32              length;
    UInt32              index;
    SInt32              value;
 
    pRawData = (UInt8*)theData;
 
    switch (gPrint_AsType)
    {
        case kPrint_AsHex:
        case kPrint_AsBinary:
            for (i = 0; i < gMaxPacketSize; i++)
            {
                switch (gPrint_AsType)
                {
                    case kPrint_AsHex:
                        safeprintf("$%02X ", pRawData[i]);
                        break;
 
                    case kPrint_AsBinary:
                        safeprintf("%d%d%d%d%d%d%d%d ", !!(pRawData[i]&0x80), !!(pRawData[i]&0x40), 
                                                        !!(pRawData[i]&0x20), !!(pRawData[i]&0x10),
                                                        !!(pRawData[i]&0x08), !!(pRawData[i]&0x04), 
                                                        !!(pRawData[i]&0x02), !!(pRawData[i]&0x01));
                        break;
                }
            }
            break;
 
        case kPrint_ParseButtons:
            length = gMaxUsageListLength;
 
            status = HIDGetButtons (kHIDInputReport, 0, gCurrentDownButtons, &length, 
                                        gPreparsedDataRef, theData, gMaxPacketSize);
            if (status)
                safeprintf("HIDGetButtons error (%d)\n", status);
                                        
            safeprintf("%d buttons down:   ", length);
 
            for (i = 0; i < length; i++)
                safeprintf("%d(%d) ", gCurrentDownButtons[i].usage, gCurrentDownButtons[i].usagePage);
            break;
 
        case kPrint_ParseBoth:
            status = HidP_PrintAllInputValues (0,(HIDPreparsedDataPtr) gPreparsedDataRef, theData, gMaxPacketSize);
 
            if (status)
                safeprintf("HidP_PrintAllInputValues error (%d)\n", status);
            break;
 
        case kPrint_SimulateISp:
            for (index = 0; index < gHIDElementCount; index++)
            {
                value = ParseElementValue (index, theData);
 
                safeprintf("%ld, ", value);
            }
            break;
    }
 
    safeprintf("   ");
 
    safeprintf("\n");   
}
 
OSStatus    HidP_PrintAllInputValues
          (UInt32                        iCollection,
          HIDPreparsedDataPtr       ptPreparsedData,
           char                      *psReport,
           int                        iReportLength)
{
    HIDCollection *ptCollection;
    HIDReportItem *ptReportItem;
    UInt32 iR, iE, iU;
    SInt32 iValue;
    UInt32 iStart;
    UInt32 iReportItem;
    HIDUsageAndPage tUsageAndPage;
/*
 *  Disallow Null Pointers
*/
    if ((ptPreparsedData == NULL) || (psReport == NULL))
        return kHIDNullPointerErr;
    if (ptPreparsedData->hidTypeIfValid != kHIDOSType)
        return kHIDInvalidPreparsedDataErr;
/*
 *  Search only the scope of the Collection specified
 *  Go through the ReportItems
 *  Filter on ReportType
*/
    ptCollection = &ptPreparsedData->collections[iCollection];
 
#if 0
safeprintf ("Collection: count: %d\n", ptCollection->reportItemCount);
#endif
 
    for (iR=0; iR<ptCollection->reportItemCount; iR++)
    {
        iReportItem = ptCollection->firstReportItem + iR;
        ptReportItem = &ptPreparsedData->reportItems[iReportItem];
 
#if 0
safeprintf ("Report Item: Type:%d, Size:%d, Start:%d, Count:%d, %s, %s\n", 
                    ptReportItem->reportType,
                    ptReportItem->globals.reportSize,
                    ptReportItem->startBit,
                    ptReportItem->globals.reportCount,
                    (ptReportItem->dataModes & kHIDDataArrayBit) == kHIDDataArray ? "isArray" : "notArray",
                    (ptReportItem->dataModes & kHIDDataConstantBit) == kHIDDataConstant ? "isConstant" : "notConstant");
#endif
 
 
        if ((ptReportItem->reportType == kHIDInputReport) &&
            (ptReportItem->dataModes & kHIDDataConstantBit) != kHIDDataConstant)
        {
/*
 *          Values
*/
            if (HIDIsVariable(ptReportItem))
            {
                int iUsageItem;
                
                iStart = ptReportItem->startBit;
                iUsageItem = ptReportItem->firstUsageItem;
                
                // loop for each usage item
                for (iU=0; iU<ptReportItem->usageItemCount; iU++)
                {
                    OSStatus status = kHIDSuccess;
                    HIDP_UsageItem *ptUsageItem;
                    
                    ptUsageItem = &ptPreparsedData->usageItems[iU];
                    
                    // if range, loop for each usage in range (is this right???)
                    if (ptUsageItem->isRange && false)  // ¥¥Êdiabled
                    {
                        int iUsageValue, min, max;
                        
                        min = ptUsageItem->usageMinimum;
                        max = ptUsageItem->usageMaximum;
                        if (max < min)
                        {
                            int temp;
                            temp = min; min = max; max = temp;
                        }
                        
                        for (iUsageValue = min; iUsageValue <= max; iUsageValue++)
                        {
                            status = HIDGetData(psReport, iReportLength, iStart,
                                                   ptReportItem->globals.reportSize, &iValue,
                                                   ((ptReportItem->globals.logicalMinimum < 0)
                                                  ||(ptReportItem->globals.logicalMaximum < 0)));
                            if (status)
                                safeprintf ("\nHidP_GetData error = %d\n", status);
 
                            iStart += ptReportItem->globals.reportSize;
            
                            safeprintf("%d:\t%d (%x) ", iUsageValue, iValue, iValue);
 
    /*
    *                       Try to scale the data
    */
#if 0
                            status = HIDScaleUsageValueIn(ptReportItem,iValue,&iValue);
                            if (status)
                                safeprintf ("\nHidP_ScaleUsageValueIn error = %d\n", status);
                            else
                                safeprintf("[%d (%x)]  ", iValue, iValue);
#endif
                        }
                    }
                    else // not range
                    {
                        status = HIDGetData(psReport, iReportLength, iStart,
                                               ptReportItem->globals.reportSize, &iValue,
                                               ((ptReportItem->globals.logicalMinimum < 0)
                                              ||(ptReportItem->globals.logicalMaximum < 0)));
                        if (status)
                            safeprintf ("\nHidP_GetData error = %d\n", status);
 
                        iStart += ptReportItem->globals.reportSize;
        
                        safeprintf("%d(%d-%d):\t%d (%x) ", 
                            ptUsageItem->usage, ptUsageItem->usageMinimum, ptUsageItem->usageMinimum, 
                            iValue, iValue);
 
#if 0
/*
*                       Try to scale the data
*/
 
                        status = HIDScaleUsageValueIn(ptReportItem,iValue,&iValue);
                        if (status)
                            safeprintf ("\nHidP_ScaleUsageValueIn error = %d\n", status);
                        else
                            safeprintf("[%d (%x)]  ", iValue, iValue);
#endif                          
                    }
                }
           }
 
/*
 *          Buttons
*/
 
            else    // is button
            {
    /*
     *          Save Arrays and Bitmaps
    */
                iStart = ptReportItem->startBit;
                for (iE=0; iE<ptReportItem->globals.reportCount; iE++)
                {
                    OSStatus status = kHIDSuccess;
                    
    #if 0
    safeprintf ("startbits = %d (%x) of report item %d:%d\n", iStart, iStart, iR, iE);
    #endif
 
                    if ((ptReportItem->dataModes & kHIDDataArrayBit) == kHIDDataArray)
                    {
                        status = HIDGetData(psReport, iReportLength, iStart, ptReportItem->globals.reportSize, &iValue, false);
                        if (status)
                            safeprintf ("\nHidP_GetData error = %d\n", status);
    #if 0
                        else
                            safeprintf ("Value = %d (%x) of array item %d:%d\n", iValue, iValue, iE, iR);
    #endif
                        iStart += ptReportItem->globals.reportSize;
                        HIDUsageAndPageFromIndex((HIDPreparsedDataRef)ptPreparsedData,ptReportItem,ptReportItem->globals.logicalMinimum+iE, &tUsageAndPage);
                        safeprintf("%d:%d ", tUsageAndPage.usage, tUsageAndPage.usagePage);
 
                    }
                    else
                    {
                        iValue = 0;
                        status = HIDGetData(psReport, iReportLength, iStart, 1, &iValue, false);
                        if (status)
                            safeprintf ("\nHidP_GetData error = %d\n", status);
    #if 0
                        else
                            safeprintf ("Value = %d (%x) of item %d:%d\n", iValue, iValue, iE, iR);
    #endif
                            
                        iStart++;
                        if (iValue != 0)
                        {
                            HIDUsageAndPageFromIndex((HIDPreparsedDataRef)ptPreparsedData,ptReportItem,ptReportItem->globals.logicalMinimum+iE,&tUsageAndPage);
                            safeprintf("%d:%d ", tUsageAndPage.usage, tUsageAndPage.usagePage);
                        }
                    }
                }
            }
        }
    }
    return kHIDSuccess;
}
 
 
int safeprintf(const char *format, ...)
{
    va_list arglist;
    int return_value = 0;
    long len;
    
    va_start(arglist, format);
    return_value= vsprintf(&gInsertPrintfBuffer[gInsertIndex], format, arglist);
    va_end(arglist);
    
    len = strlen(&gInsertPrintfBuffer[gInsertIndex]);
    
    if (gInsertIndex + len < kPrintfBufferSize)
        gInsertIndex += len;
 
    return return_value;
}
 
void PrintPreparsedData (void)
{
    HIDPreparsedDataPtr     parsedData = (HIDPreparsedDataPtr) gPreparsedDataRef;
    char                    indent[] = "  ";
    
    printf ("\n_Collections_\n");
    PrintCollectionItems(0, parsedData->collectionCount, indent);
 
    printf ("\n_ReportItems_\n");
    PrintReportItems(0, parsedData->reportItemCount, indent);
 
    printf ("\n_UsageItems_\n");
    PrintUsageItems(0, parsedData->usageItemCount, indent);
 
    printf ("\n_StringItems_: %d items\n", parsedData->stringItemCount);
 
    printf ("\n_DesigItems_: %d items\n", parsedData->desigItemCount);
    
    if (parsedData->stringItemCount > 0 || parsedData->desigItemCount > 0)
        printf ("¥¥¥ Wow, it has strings or physical designators!\n");
    
    printf ("\n\n");
}
 
void PrintCollectionItems (UInt32 firstCollection, UInt32 collectionCount, char * indent)
{
    HIDPreparsedDataPtr     parsedData = (HIDPreparsedDataPtr) gPreparsedDataRef;
    unsigned                index, start, end;
    char                    subIndent[32];
    
    sprintf (subIndent, "\t%s", indent);
    
    start = firstCollection;
    end = start + collectionCount;
    
    if (end > parsedData->collectionCount)
    {
        printf ("%s¥¥¥!!!collection integrity error! (%d, %d)\n", indent, end, parsedData->collectionCount);
        return;
    }
    else for (index = start; index < end; index++)
    {
        printf ("%sCollection (%X): %d (parent:%d, children:%d, firstChild:%d, nextSibling:%d)\n", 
            indent,
            index,
            parsedData->collections[index].data,
            parsedData->collections[index].parent,
            parsedData->collections[index].children,
            parsedData->collections[index].firstChild,
            parsedData->collections[index].nextSibling);
        
        // print usage items in collection
        PrintUsageItems    (parsedData->collections[index].firstUsageItem, 
                            parsedData->collections[index].usageItemCount, 
                            subIndent);
 
        // print report items in collection
        PrintReportItems   (parsedData->collections[index].firstReportItem, 
                            parsedData->collections[index].reportItemCount, 
                            subIndent);
 
    }
}
 
void PrintReportItems (UInt32 firstReportItem, UInt32 reportItemCount, char * indent)
{
    HIDPreparsedDataPtr     parsedData = (HIDPreparsedDataPtr) gPreparsedDataRef;
    unsigned                index, start, end;
    char                    subIndent[32];
    
    sprintf (subIndent, "\t%s", indent);
    
    start = firstReportItem;
    end = start + reportItemCount;
    
    if (end > parsedData->reportItemCount)
    {
        printf ("%s¥¥¥!!!report integrity error! (%d, %d)\n", indent, end, parsedData->reportItemCount);
        return;
    }
    else for (index = start; index < end; index++)
    {
        printf ("%sReportItem(%d): Page:%d\t(%d<->%d)\t(%d<->%d)\t[%d+(%d*%d)] %s %s\n",
            indent,
            index, 
            parsedData->reportItems[index].globals.usagePage, 
            parsedData->reportItems[index].globals.logicalMinimum, 
            parsedData->reportItems[index].globals.logicalMaximum, 
            parsedData->reportItems[index].globals.physicalMinimum, 
            parsedData->reportItems[index].globals.physicalMaximum, 
            parsedData->reportItems[index].startBit, 
            parsedData->reportItems[index].globals.reportSize, 
            parsedData->reportItems[index].globals.reportCount,
            (parsedData->reportItems[index].dataModes & kHIDDataConstantBit) ? "isConstant" : "",
            (parsedData->reportItems[index].dataModes & kHIDDataArrayBit) ? "isArray" : "");
 
        // print usage items in report item
        PrintUsageItems    (parsedData->reportItems[index].firstUsageItem, 
                            parsedData->reportItems[index].usageItemCount, 
                            subIndent);
 
        if (parsedData->reportItems[index].stringItemCount > 0 || parsedData->reportItems[index].desigItemCount > 0)
            printf ("%sWow, it has strings or physical designators!\n", indent);
    }
}
 
void PrintUsageItems (UInt32 firstUsageItem, UInt32 usageItemCount, char * indent)
{
    HIDPreparsedDataPtr     parsedData = (HIDPreparsedDataPtr) gPreparsedDataRef;
    unsigned                index, start, end;
 
    start = firstUsageItem;
    end = start + usageItemCount;
    
    if (end > parsedData->usageItemCount)
    {
        printf ("%s¥¥¥!!!usage integrity error! (%d, %d)\n", indent, end, parsedData->usageItemCount);
        return;
    }
    else for (index = start; index < end; index++)
    {
        if (parsedData->usageItems[index].isRange)
            printf ("%sUsage(%X): %d-%d\n", indent, index, 
                parsedData->usageItems[index].usageMinimum, 
                parsedData->usageItems[index].usageMaximum);
        else
            printf ("%sUsage(%d): %d\n", indent, index, parsedData->usageItems[index].usage);
    }
}
 
void SimulateISpEnumeration (void)
{
    OSStatus            err = noErr;
    UInt32              count;
 
 
    // get the overall capabilities of the device
    if (err == noErr)
        err = HIDGetCaps (gPreparsedDataRef, &gHIDCaps);
 
    // ¥¥¥ for now, only work with devices that appear to be a joystick or gamepad
    if (err == noErr)
    {
        if (gHIDCaps.usagePage != kHIDPage_Generic ||
            (gHIDCaps.usage != kHIDUsage_Joystick && gHIDCaps.usage != kHIDUsage_Gamepad))
        {
            printf("¥¥ matched to non-joystick, non-gamepad... bailing out\n");
            err = fnfErr;
        }
    }
    
    // allocate space for button caps
    if (err == noErr)
    {
        gHIDButtonCaps = (HIDButtonCaps *) NewPtrSysClear(sizeof(HIDButtonCaps) * gHIDCaps.numberInputButtonCaps);
        if (gHIDButtonCaps == nil) printf("¥¥ could not allocate space for buttonCaps\n");
        if (gHIDButtonCaps == nil) err = memFullErr;
    }
 
    // get the button capabilities
    if (err == noErr)
    {
        count = gHIDCaps.numberInputButtonCaps;
        err = HIDGetButtonCaps (kHIDInputReport, gHIDButtonCaps, &gHIDCaps.numberInputButtonCaps, gPreparsedDataRef);
        
        if (count > gHIDCaps.numberInputButtonCaps)
            printf ("¥¥ number of button caps increased! (%d->%d)\n", gHIDCaps.numberInputButtonCaps, count);
        else
            gHIDCaps.numberInputButtonCaps = count;
    }
 
    // allocate space for value caps
    if (err == noErr)
    {
        gHIDValueCaps = (HIDValueCaps *) NewPtrSysClear(sizeof(HIDValueCaps) * gHIDCaps.numberInputValueCaps);
        if (gHIDValueCaps == nil) printf("¥¥ could not allocate space for valueCaps\n");
        if (gHIDValueCaps == nil) err = memFullErr;
    }
 
    // get the value capabilities
    if (err == noErr)
        err = HIDGetValueCaps (kHIDInputReport, gHIDValueCaps, &gHIDCaps.numberInputValueCaps, gPreparsedDataRef);
    
    // build HID elements
    if (err == noErr)
        err = BuildHIDElements ();
 
    // print HID elements
    if (err == noErr)
        err = PrintHIDElements ();
    
    // report error
    if (err)
        printf ("¥¥Êerror = %d\n", err);
}
 
OSStatus PrintHIDElements (void)
{
    UInt32  index;
    
    printf ("\nHIDElements (%d):\n", gHIDElementCount);
    for (index = 0; index < gHIDElementCount; index++)
    {
        switch (gHIDElements[index].type)
        {
            case kButtonElement:
                printf ("%2.2d:Button (%d): %X:%d, key:%d, (%d<->%d), str:%d\n",
                    index,
                    gHIDElements[index].capsIndex,
                    gHIDElements[index].usage,
                    gHIDElements[index].usagePage,
                    gHIDElements[index].key,
                    gHIDElements[index].min,
                    gHIDElements[index].max,
                    gHIDElements[index].stringIndex
                    );
                break;
                
            case kValueElement:
                printf ("%2.2d:Value (%d): %X:%d, key:%d, (%d<->%d), str:%d\n",
                    index,
                    gHIDElements[index].capsIndex,
                    gHIDElements[index].usage,
                    gHIDElements[index].usagePage,
                    gHIDElements[index].key,
                    gHIDElements[index].min,
                    gHIDElements[index].max,
                    gHIDElements[index].stringIndex
                    );
                break;
        }
    }
    
    printf ("\n");
    
    return noErr;
}
 
OSStatus BuildHIDElements (void)
{
    OSStatus            err = noErr;
    OSStatus            initErr;
    UInt32              hidElementIndex = 0;
    UInt32              valueIndex;
    UInt32              buttonIndex;
    HIDUsage            usage;
    
    // allocate space for HID elements
    if (err == noErr)
    {
        gHIDElementCount = CountHIDElements();
        gHIDElements = (HIDElement *) NewPtrSysClear(sizeof(HIDElement) * gHIDElementCount);
        if (gHIDElements == nil) printf("¥¥ could not allocate space for HIDElements\n");
        if (gHIDElements == nil) err = memFullErr;
    }
 
    // add all values to the elements
    if (err == noErr)
        for (valueIndex = 0; valueIndex < gHIDCaps.numberInputValueCaps; valueIndex++)
        {
            HIDElement hidElement;
 
            if (gHIDValueCaps[valueIndex].reportCount != 1)
                printf("BuildHIDElements: value caps reportCount is not 1!\n");
                
            if (gHIDValueCaps[valueIndex].isRange)
                printf("BuildHIDElements: value caps is range!\n");
 
            hidElement.type = kValueElement;
            hidElement.capsIndex = valueIndex;
            hidElement.usagePage = gHIDValueCaps[valueIndex].usagePage;
            hidElement.min = gHIDValueCaps[valueIndex].logicalMin;
            hidElement.max = gHIDValueCaps[valueIndex].logicalMax;
            
            // NOTE: no devices I can find have values that are ranges, so this code may not be right!
            if (gHIDValueCaps[valueIndex].isRange)
            {
                for    (usage = gHIDValueCaps[valueIndex].u.range.usageMin;
                        usage <= gHIDValueCaps[valueIndex].u.range.usageMax;
                        usage++)
                {
                    hidElement.usage = usage;
                    initErr = InitializeHIDElement(&hidElement);
                    
                    if (initErr == noErr)
                        gHIDElements[hidElementIndex++] = hidElement;
                }
            }
            else // notRange
            {
                hidElement.usage = gHIDValueCaps[valueIndex].u.notRange.usage;
                initErr = InitializeHIDElement(&hidElement);
                
                if (initErr == noErr)
                    gHIDElements[hidElementIndex++] = hidElement;
            }
        }
 
    // add all buttons to the elements
    if (err == noErr)
        for (buttonIndex = 0; buttonIndex < gHIDCaps.numberInputButtonCaps; buttonIndex++)
        {
            HIDElement hidElement;
            hidElement.type = kButtonElement;
            hidElement.capsIndex = buttonIndex;
            hidElement.usagePage = gHIDButtonCaps[buttonIndex].usagePage;
            hidElement.min = 0;
            hidElement.max = 1;
            
            printf("enumerating button (%d)\n", buttonIndex);
 
            // most buttons are reported as ranges of usage values
            if (gHIDButtonCaps[buttonIndex].isRange)
            {
                for    (usage = gHIDButtonCaps[buttonIndex].u.range.usageMin;
                        usage <= gHIDButtonCaps[buttonIndex].u.range.usageMax;
                        usage++)
                {
                    hidElement.usage = usage;
                    initErr = InitializeHIDElement(&hidElement);
                    
                    if (initErr == noErr)
                        gHIDElements[hidElementIndex++] = hidElement;
                }
            }
            else // notRange
            {
                hidElement.usage = gHIDButtonCaps[buttonIndex].u.notRange.usage;
                initErr = InitializeHIDElement(&hidElement);
                
                if (initErr == noErr)
                    gHIDElements[hidElementIndex++] = hidElement;
            }
        }
 
    return err;
}
 
UInt32 CountHIDElements(void)
{
    OSStatus            err = noErr;
    UInt32              count = 0;
    UInt32              valueIndex;
    UInt32              buttonIndex;
    
    if (err == noErr)
        for (valueIndex = 0; valueIndex < gHIDCaps.numberInputValueCaps; valueIndex++)
        {
            if (gHIDValueCaps[valueIndex].isRange)
                count += (gHIDValueCaps[valueIndex].u.range.usageMax - gHIDValueCaps[valueIndex].u.range.usageMin) + 1;
            else // notRange
                count++;
        }
 
    // count all buttons
    if (err == noErr)
        for (buttonIndex = 0; buttonIndex < gHIDCaps.numberInputButtonCaps; buttonIndex++)
        {
            if (gHIDButtonCaps[buttonIndex].isRange)
                count += (gHIDButtonCaps[buttonIndex].u.range.usageMax - gHIDButtonCaps[buttonIndex].u.range.usageMin) + 1;
            else // notRange
                count++;
        }
    
    return count;
}
 
OSStatus InitializeHIDElement(HIDElement * hidElement)
{
    OSStatus            err = noErr;
    SInt32              stringIndex = -1;
 
    switch (hidElement->type)
    {
        case kButtonElement:
            if (hidElement->usagePage != kHIDPage_Button)
                printf("InitializeHIDElement: button found not on button page (%d)\n", hidElement->usagePage);
 
            if (hidElement->usagePage != kHIDPage_Button)
                return kHIDBadParameterErr;
            
            switch (hidElement->usage)
            {
                case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: 
                case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16:
                    hidElement->key = kUSB_button1_element + (hidElement->usage - 1);
                    stringIndex = kStrList_DefaultNames_Button1 + (hidElement->usage - 1);
                    break;
                default:
                    return kHIDBadParameterErr;
            }
            break;
 
        case kValueElement:
            if (hidElement->usagePage == kHIDPage_Generic)
                switch (hidElement->usage)
                {
                    case kHIDUsage_X:
                        hidElement->key = kUSB_xaxis_element;
                        stringIndex = kStrList_DefaultNames_XAxis;
                        break;
 
                    case kHIDUsage_Y:
                        hidElement->key = kUSB_yaxis_element;
                        stringIndex = kStrList_DefaultNames_YAxis;
                        break;
 
                    case kHIDUsage_Z:
                        hidElement->key = kUSB_axis_element;
                        stringIndex = kStrList_DefaultNames_Axis;
                        break;
 
                    case kHIDUsage_Rx:
                        hidElement->key = kUSB_axis_element;
                        stringIndex = kStrList_DefaultNames_Axis;
                        break;
 
                    case kHIDUsage_Ry:
                        hidElement->key = kUSB_axis_element;
                        stringIndex = kStrList_DefaultNames_Axis;
                        break;
 
                    case kHIDUsage_Rz:
                        hidElement->key = kUSB_rudder_element;
                        stringIndex = kStrList_DefaultNames_Twist;
                        break;
 
                    case kHIDUsage_Slider:
                        hidElement->key = kUSB_throttle_element;
                        stringIndex = kStrList_DefaultNames_Throttle;
                        break;
 
                    case kHIDUsage_Dial:
                    case kHIDUsage_Wheel:
                        hidElement->key = kUSB_trim_element;
                        stringIndex = kStrList_DefaultNames_Trim;
                        break;
 
                    case kHIDUsage_HatSwitch:
                        hidElement->key = kUSB_povhat_element;
                        stringIndex = kStrList_DefaultNames_POVHat;
                        break;
                }
            else if (hidElement->usagePage == kHIDPage_Simulation)
                switch (hidElement->usage)
                {
                    case kHIDUsage_Rudder:
                        hidElement->key = kUSB_rudder_element;
                        stringIndex = kStrList_DefaultNames_Rudder;
                        break;
 
                    case kHIDUsage_Throttle:
                        hidElement->key = kUSB_throttle_element;
                        stringIndex = kStrList_DefaultNames_Throttle;
                        break;
                }
            break;
 
        default:
            printf("InitializeHIDElement: unknown type (%d)\n", hidElement->type);
            err = kHIDBadParameterErr;
            break;
    }
    
    hidElement->stringIndex = stringIndex;
        
    return err;
}
 
SInt32 ParseElementValue (UInt32 inElementIndex, Ptr inBuffer)
{
    OSStatus        err = noErr;
    HIDUsage        usageList[kUSB_button16_element];
    UInt32          usageListSize = kUSB_button16_element;
    UInt32          index;
    HIDElement *    hidElement = &gHIDElements[inElementIndex];
    SInt32          usageValue;
    
    switch (hidElement->type)
    {
        case kButtonElement:
            // for now, we'll only look at buttons on the button page
            err = HIDGetButtonsOnPage  (kHIDInputReport, 
                                        kHIDPage_Button, 
                                        0,
                                        usageList,
                                        &usageListSize,
                                        gPreparsedDataRef,
                                        inBuffer,
                                        gMaxPacketSize);
            
            // confirm we succeeded
            if (err != noErr && err != kHIDUsageNotFoundErr)
                safeprintf ("¥¥ParseElementValue: HIDGetButtonsOnPage failed (%d)", err);
            if (err != noErr)
                break;
            
            // walk the returned usage list
            for (index = 0; index < usageListSize; index++)
            {
                // if our usage is in the usage list, then we are down
                if (usageList[index] == hidElement->usage)
                    return 1;
            }
            
            // we were not in the list, so we are up
            return 0;
            
        case kValueElement:
            // get the usage value
            err = HIDGetUsageValue (kHIDInputReport,
                                    hidElement->usagePage,
                                    0,  // ¥¥ this might be faster if we store collection and pass here
                                    hidElement->usage,
                                    &usageValue,
                                    gPreparsedDataRef,
                                    inBuffer,
                                    gMaxPacketSize);
 
            // confirm we succeeded
            if (err != noErr && err != kHIDUsageNotFoundErr)
                safeprintf ("¥¥ParseElementValue: HIDGetUsageValue failed (%d)", err);
            if (err != noErr)
                break;
            
            // handle dpads
            if (false && hidElement->key == kUSB_povhat_element)
            {
                if (usageValue < hidElement->min || usageValue > hidElement->max)
                    usageValue = kISpPadIdle;
                else
                {
                    SInt32  normalized = usageValue - hidElement->min;
                    SInt32  normalizedMax = hidElement->max - hidElement->min;
                    
                    // avoid devide by zero
                    if (normalizedMax == 0) return kISpPadIdle;
                    
                    // rescale value
                    normalized = ((normalized * 7)) / normalizedMax;
                    switch (normalized)
                    {
                        case 0: usageValue = kISpPadUp;         break;
                        case 1: usageValue = kISpPadUpRight;    break;
                        case 2: usageValue = kISpPadRight;      break;
                        case 3: usageValue = kISpPadDownRight;  break;
                        case 4: usageValue = kISpPadDown;       break;
                        case 5: usageValue = kISpPadDownLeft;   break;
                        case 6: usageValue = kISpPadLeft;       break;
                        case 7: usageValue = kISpPadUpLeft;     break;
 
                        default:usageValue = kISpPadIdle;       break; // should not get here
                    }
                }
            }
            
            return usageValue;
    }
    
    return 0;
}