/* * Support for Mac OS X via the HID Utilities example code. HID Utilities * talks to very low-level parts of the HID Manager API, which are deprecated * in OS X 10.5. Please see macosx_hidmanager.c for the 10.5 implementation. * * Please see the file LICENSE in the source's root directory. * * This file written by Ryan C. Gordon. */ #include "manymouse.h" #if ( (defined(__MACH__)) && (defined(__APPLE__)) ) /* * This source is almost entirely lifted from Apple's HID Utilities * example source code, written by George Warner: * * http://developer.apple.com/samplecode/HID_Utilities_Source/HID_Utilities_Source.html * * The source license to HID Utilities allows this sort of blatant stealing. * * Patches to HID Utilities have comments like "ryan added this", otherwise, * I just tried to cut down that package to the smallest set of functions * I needed. * * Scroll down for "-- END HID UTILITIES --" to see the ManyMouse glue code. */ #include #include // 10.0.x //#include // 10.1.x #include #include #include #include #include #define USE_NOTIFICATIONS 1 #define HIDREPORTERRORNUM(s,n) do {} while (false) #define HIDREPORTERROR(s) do {} while (false) typedef enum HIDElementTypeMask { kHIDElementTypeInput = 1 << 1, kHIDElementTypeOutput = 1 << 2, kHIDElementTypeFeature = 1 << 3, kHIDElementTypeCollection = 1 << 4, kHIDElementTypeIO = kHIDElementTypeInput | kHIDElementTypeOutput | kHIDElementTypeFeature, kHIDElementTypeAll = kHIDElementTypeIO | kHIDElementTypeCollection }HIDElementTypeMask; enum { kDefaultUserMin = 0, // default user min and max used for scaling kDefaultUserMax = 255 }; enum { kDeviceQueueSize = 50 // this is wired kernel memory so should be set to as small as possible // but should account for the maximum possible events in the queue // USB updates will likely occur at 100 Hz so one must account for this rate of // if states change quickly (updates are only posted on state changes) }; struct recElement { unsigned long type; // the type defined by IOHIDElementType in IOHIDKeys.h long usagePage; // usage page from IOUSBHIDParser.h which defines general usage long usage; // usage within above page from IOUSBHIDParser.h which defines specific usage void * cookie; // unique value (within device of specific vendorID and productID) which identifies element, will NOT change long min; // reported min value possible long max; // reported max value possible long scaledMin; // reported scaled min value possible long scaledMax; // reported scaled max value possible long size; // size in bits of data return from element unsigned char relative; // are reports relative to last report (deltas) unsigned char wrapping; // does element wrap around (one value higher than max is min) unsigned char nonLinear; // are the values reported non-linear relative to element movement unsigned char preferredState; // does element have a preferred state (such as a button) unsigned char nullState; // does element have null state long units; // units value is reported in (not used very often) long unitExp; // exponent for units (also not used very often) char name[256]; // name of element (c string) // runtime variables long calMin; // min returned value long calMax; // max returned value (calibrate call) long userMin; // user set value to scale to (scale call) long userMax; struct recElement * pPrevious; // previous element (NULL at list head) struct recElement * pChild; // next child (only of collections) struct recElement * pSibling; // next sibling (for elements and collections) long depth; }; typedef struct recElement recElement; typedef recElement* pRecElement; // ryan added this. typedef enum { DISCONNECT_CONNECTED, DISCONNECT_TELLUSER, DISCONNECT_COMPLETE } DisconnectState; struct recDevice { void * interface; // interface to device, NULL = no interface void * queue; // device queue, NULL = no queue void * queueRunLoopSource; // device queue run loop source, NULL == no source void * transaction; // output transaction interface, NULL == no interface void * notification; // notifications char transport[256]; // device transport (c string) long vendorID; // id for device vendor, unique across all devices long productID; // id for particular product, unique across all of a vendors devices long version; // version of product char manufacturer[256]; // name of manufacturer char product[256]; // name of product char serial[256]; // serial number of specific product, can be assumed unique across specific product or specific vendor (not used often) long locID; // long representing location in USB (or other I/O) chain which device is pluged into, can identify specific device on machine long usage; // usage page from IOUSBHID Parser.h which defines general usage long usagePage; // usage within above page from IOUSBHID Parser.h which defines specific usage long totalElements; // number of total elements (should be total of all elements on device including collections) (calculated, not reported by device) long features; // number of elements of type kIOHIDElementTypeFeature long inputs; // number of elements of type kIOHIDElementTypeInput_Misc or kIOHIDElementTypeInput_Button or kIOHIDElementTypeInput_Axis or kIOHIDElementTypeInput_ScanCodes long outputs; // number of elements of type kIOHIDElementTypeOutput long collections; // number of elements of type kIOHIDElementTypeCollection long axis; // number of axis (calculated, not reported by device) long buttons; // number of buttons (calculated, not reported by device) long hats; // number of hat switches (calculated, not reported by device) long sliders; // number of sliders (calculated, not reported by device) long dials; // number of dials (calculated, not reported by device) long wheels; // number of wheels (calculated, not reported by device) recElement* pListElements; // head of linked list of elements DisconnectState disconnect; // (ryan added this.) AbsoluteTime lastScrollTime; // (ryan added this.) struct recDevice* pNext; // next device }; typedef struct recDevice recDevice; typedef recDevice* pRecDevice; #if USE_NOTIFICATIONS static IONotificationPortRef gNotifyPort; static io_iterator_t gAddedIter; static CFRunLoopRef gRunLoop; #endif USE_NOTIFICATIONS // for element retrieval static pRecDevice gCurrentGetDevice = NULL; static Boolean gAddAsChild = false; static int gDepth = false; static pRecDevice gpDeviceList = NULL; static UInt32 gNumDevices = 0; static Boolean HIDIsValidDevice(const pRecDevice pSearchDevice); static pRecElement HIDGetFirstDeviceElement (pRecDevice pDevice, HIDElementTypeMask typeMask); static pRecElement HIDGetNextDeviceElement (pRecElement pElement, HIDElementTypeMask typeMask); static pRecDevice HIDGetFirstDevice (void); static pRecDevice HIDGetNextDevice (pRecDevice pDevice); static void HIDReleaseDeviceList (void); static unsigned long HIDDequeueDevice (pRecDevice pDevice); static void hid_GetElements (CFTypeRef refElementCurrent, pRecElement *ppCurrentElement); static void HIDReportError(const char *err) {} static void HIDReportErrorNum(const char *err, int num) {} static void hid_GetCollectionElements (CFMutableDictionaryRef deviceProperties, pRecElement *ppCurrentCollection) { CFTypeRef refElementTop = CFDictionaryGetValue (deviceProperties, CFSTR(kIOHIDElementKey)); if (refElementTop) hid_GetElements (refElementTop, ppCurrentCollection); else HIDReportError ("hid_GetCollectionElements: CFDictionaryGetValue error when creating CFTypeRef for kIOHIDElementKey."); } // extracts actual specific element information from each element CF dictionary entry static void hid_GetElementInfo (CFTypeRef refElement, pRecElement pElement) { long number; CFTypeRef refType; // type, usagePage, usage already stored refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementCookieKey)); if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number)) pElement->cookie = (IOHIDElementCookie) number; else pElement->cookie = (IOHIDElementCookie) 0; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementMinKey)); if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number)) pElement->min = number; else pElement->min = 0; pElement->calMax = pElement->min; pElement->userMin = kDefaultUserMin; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementMaxKey)); if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number)) pElement->max = number; else pElement->max = 0; pElement->calMin = pElement->max; pElement->userMax = kDefaultUserMax; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementScaledMinKey)); if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number)) pElement->scaledMin = number; else pElement->scaledMin = 0; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementScaledMaxKey)); if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number)) pElement->scaledMax = number; else pElement->scaledMax = 0; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementSizeKey)); if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number)) pElement->size = number; else pElement->size = 0; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsRelativeKey)); if (refType) pElement->relative = CFBooleanGetValue (refType); else pElement->relative = 0; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsWrappingKey)); if (refType) pElement->wrapping = CFBooleanGetValue (refType); else pElement->wrapping = false; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsNonLinearKey)); if (refType) pElement->nonLinear = CFBooleanGetValue (refType); else pElement->wrapping = false; #ifdef kIOHIDElementHasPreferredStateKey refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementHasPreferredStateKey)); #else // Mac OS X 10.0 has spelling error refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementHasPreferedStateKey)); #endif if (refType) pElement->preferredState = CFBooleanGetValue (refType); else pElement->preferredState = false; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementHasNullStateKey)); if (refType) pElement->nullState = CFBooleanGetValue (refType); else pElement->nullState = false; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementUnitKey)); if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number)) pElement->units = number; else pElement->units = 0; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementUnitExponentKey)); if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number)) pElement->unitExp = number; else pElement->unitExp = 0; refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementNameKey)); if (refType) if (!CFStringGetCString (refType, pElement->name, 256, CFStringGetSystemEncoding ())) HIDReportError ("CFStringGetCString error retrieving pElement->name."); #if 0 if (!*pElement->name) { // set name from vendor id, product id & usage info look up if (!HIDGetElementNameFromVendorProductUsage (gCurrentGetDevice->vendorID, gCurrentGetDevice->productID, pElement->usagePage, pElement->usage, pElement->name)) { // set name from vendor id/product id look up HIDGetElementNameFromVendorProductCookie (gCurrentGetDevice->vendorID, gCurrentGetDevice->productID, (long) pElement->cookie, pElement->name); if (!*pElement->name) { // if no name HIDGetUsageName (pElement->usagePage, pElement->usage, pElement->name); if (!*pElement->name) // if not usage sprintf (pElement->name, "Element"); } } } #endif } static void hid_AddElement (CFTypeRef refElement, pRecElement * ppElementCurrent) { pRecDevice pDevice = gCurrentGetDevice; pRecElement pElement = NULL; long elementType, usagePage, usage; CFTypeRef refElementType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementTypeKey)); CFTypeRef refUsagePage = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementUsagePageKey)); CFTypeRef refUsage = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementUsageKey)); if (refElementType) CFNumberGetValue (refElementType, kCFNumberLongType, &elementType); if (refUsagePage) CFNumberGetValue (refUsagePage, kCFNumberLongType, &usagePage); if (refUsage) CFNumberGetValue (refUsage, kCFNumberLongType, &usage); if (NULL == pDevice) return; if (elementType) { // look at types of interest if (elementType != kIOHIDElementTypeCollection) { if (usagePage && usage) // if valid usage and page { switch (usagePage) // only interested in kHIDPage_GenericDesktop and kHIDPage_Button { case kHIDPage_GenericDesktop: { switch (usage) // look at usage to determine function { case kHIDUsage_GD_X: case kHIDUsage_GD_Y: case kHIDUsage_GD_Z: case kHIDUsage_GD_Rx: case kHIDUsage_GD_Ry: case kHIDUsage_GD_Rz: pElement = (pRecElement) malloc (sizeof (recElement)); if (pElement) pDevice->axis++; break; case kHIDUsage_GD_Slider: pElement = (pRecElement) malloc (sizeof (recElement)); if (pElement) pDevice->sliders++; break; case kHIDUsage_GD_Dial: pElement = (pRecElement) malloc (sizeof (recElement)); if (pElement) pDevice->dials++; break; case kHIDUsage_GD_Wheel: pElement = (pRecElement) malloc (sizeof (recElement)); if (pElement) pDevice->wheels++; break; case kHIDUsage_GD_Hatswitch: pElement = (pRecElement) malloc (sizeof (recElement)); if (pElement) pDevice->hats++; break; default: pElement = (pRecElement) malloc (sizeof (recElement)); break; } } break; case kHIDPage_Button: pElement = (pRecElement) malloc (sizeof (recElement)); if (pElement) pDevice->buttons++; break; default: // just add a generic element pElement = (pRecElement) malloc (sizeof (recElement)); break; } } #if 0 else HIDReportError ("CFNumberGetValue error when getting value for refUsage or refUsagePage."); #endif 0 } else // collection pElement = (pRecElement) malloc (sizeof (recElement)); } else HIDReportError ("CFNumberGetValue error when getting value for refElementType."); if (pElement) // add to list { // this code builds a binary tree based on the collection hierarchy of inherent in the device element layout // it preserves the structure of the lements as collections have children and elements are siblings to each other // clear record bzero(pElement,sizeof(recElement)); // get element info pElement->type = elementType; pElement->usagePage = usagePage; pElement->usage = usage; pElement->depth = 0; // assume root object hid_GetElementInfo (refElement, pElement); // count elements pDevice->totalElements++; switch (pElement->type) { case kIOHIDElementTypeInput_Misc: case kIOHIDElementTypeInput_Button: case kIOHIDElementTypeInput_Axis: case kIOHIDElementTypeInput_ScanCodes: pDevice->inputs++; break; case kIOHIDElementTypeOutput: pDevice->outputs++; break; case kIOHIDElementTypeFeature: pDevice->features++; break; case kIOHIDElementTypeCollection: pDevice->collections++; break; default: HIDReportErrorNum ("Unknown element type : ", pElement->type); } if (NULL == *ppElementCurrent) // if at list head { pDevice->pListElements = pElement; // add current element *ppElementCurrent = pElement; // set current element to element we just added } else // have exsiting structure { if (gAddAsChild) // if the previous element was a collection, let's add this as a child of the previous { // this iteration should not be needed but there maybe some untested degenerate case which this code will ensure works while ((*ppElementCurrent)->pChild) // step down tree until free child node found *ppElementCurrent = (*ppElementCurrent)->pChild; (*ppElementCurrent)->pChild = pElement; // insert there pElement->depth = (*ppElementCurrent)->depth + 1; } else // add as sibling { // this iteration should not be needed but there maybe some untested degenerate case which this code will ensure works while ((*ppElementCurrent)->pSibling) // step down tree until free sibling node found *ppElementCurrent = (*ppElementCurrent)->pSibling; (*ppElementCurrent)->pSibling = pElement; // insert there pElement->depth = (*ppElementCurrent)->depth; } pElement->pPrevious = *ppElementCurrent; // point to previous *ppElementCurrent = pElement; // set current to our collection } if (elementType == kIOHIDElementTypeCollection) // if this element is a collection of other elements { gAddAsChild = true; // add next set as children to this element gDepth++; hid_GetCollectionElements ((CFMutableDictionaryRef) refElement, &pElement); // recursively process the collection gDepth--; } gAddAsChild = false; // add next as this elements sibling (when return from a collection or with non-collections) } #if 0 else HIDReportError ("hid_AddElement - no element added."); #endif } static void hid_GetElementsCFArrayHandler (const void * value, void * parameter) { if (CFGetTypeID (value) == CFDictionaryGetTypeID ()) hid_AddElement ((CFTypeRef) value, (pRecElement *) parameter); } // --------------------------------- // handles retrieval of element information from arrays of elements in device IO registry information static void hid_GetElements (CFTypeRef refElementCurrent, pRecElement *ppCurrentElement) { CFTypeID type = CFGetTypeID (refElementCurrent); if (type == CFArrayGetTypeID()) // if element is an array { CFRange range = {0, CFArrayGetCount (refElementCurrent)}; // CountElementsCFArrayHandler called for each array member CFArrayApplyFunction (refElementCurrent, range, hid_GetElementsCFArrayHandler, ppCurrentElement); } } static void hid_TopLevelElementHandler (const void * value, void * parameter) { CFTypeRef refCF = 0; if ((NULL == value) || (NULL == parameter)) return; // (kIOReturnBadArgument) if (CFGetTypeID (value) != CFDictionaryGetTypeID ()) return; // (kIOReturnBadArgument) refCF = CFDictionaryGetValue (value, CFSTR(kIOHIDElementUsagePageKey)); if (!CFNumberGetValue (refCF, kCFNumberLongType, &((pRecDevice) parameter)->usagePage)) HIDReportError ("CFNumberGetValue error retrieving pDevice->usagePage."); refCF = CFDictionaryGetValue (value, CFSTR(kIOHIDElementUsageKey)); if (!CFNumberGetValue (refCF, kCFNumberLongType, &((pRecDevice) parameter)->usage)) HIDReportError ("CFNumberGetValue error retrieving pDevice->usage."); } static void hid_GetDeviceInfo (io_object_t hidDevice, CFMutableDictionaryRef hidProperties, pRecDevice pDevice) { CFMutableDictionaryRef usbProperties = 0; io_registry_entry_t parent1, parent2; // Mac OS X currently is not mirroring all USB properties to HID page so need to look at USB device page also // get dictionary for usb properties: step up two levels and get CF dictionary for USB properties if ((KERN_SUCCESS == IORegistryEntryGetParentEntry (hidDevice, kIOServicePlane, &parent1)) && (KERN_SUCCESS == IORegistryEntryGetParentEntry (parent1, kIOServicePlane, &parent2)) && (KERN_SUCCESS == IORegistryEntryCreateCFProperties (parent2, &usbProperties, kCFAllocatorDefault, kNilOptions))) { if (usbProperties) { CFTypeRef refCF = 0; // get device info // try hid dictionary first, if fail then go to usb dictionary // get transport refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDTransportKey)); if (refCF) { if (!CFStringGetCString (refCF, pDevice->transport, 256, CFStringGetSystemEncoding ())) HIDReportError ("CFStringGetCString error retrieving pDevice->transport."); } // get vendorID refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDVendorIDKey)); if (!refCF) refCF = CFDictionaryGetValue (usbProperties, CFSTR("idVendor")); if (refCF) { if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->vendorID)) HIDReportError ("CFNumberGetValue error retrieving pDevice->vendorID."); } // get product ID refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDProductIDKey)); if (!refCF) refCF = CFDictionaryGetValue (usbProperties, CFSTR("idProduct")); if (refCF) { if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->productID)) HIDReportError ("CFNumberGetValue error retrieving pDevice->productID."); } // get product version refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDVersionNumberKey)); if (refCF) { if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->version)) HIDReportError ("CFNumberGetValue error retrieving pDevice->version."); } // get manufacturer name refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDManufacturerKey)); if (!refCF) refCF = CFDictionaryGetValue (usbProperties, CFSTR("USB Vendor Name")); if (refCF) { if (!CFStringGetCString (refCF, pDevice->manufacturer, 256, CFStringGetSystemEncoding ())) HIDReportError ("CFStringGetCString error retrieving pDevice->manufacturer."); } // get product name refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDProductKey)); if (!refCF) refCF = CFDictionaryGetValue (usbProperties, CFSTR("USB Product Name")); if (refCF) { if (!CFStringGetCString (refCF, pDevice->product, 256, CFStringGetSystemEncoding ())) HIDReportError ("CFStringGetCString error retrieving pDevice->product."); } // get serial refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDSerialNumberKey)); if (refCF) { if (!CFStringGetCString (refCF, pDevice->serial, 256, CFStringGetSystemEncoding ())) HIDReportError ("CFStringGetCString error retrieving pDevice->serial."); } // get location ID refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDLocationIDKey)); if (!refCF) refCF = CFDictionaryGetValue (usbProperties, CFSTR("locationID")); if (refCF) { if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->locID)) HIDReportError ("CFNumberGetValue error retrieving pDevice->locID."); } // get usage page and usage refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDPrimaryUsagePageKey)); if (refCF) { if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->usagePage)) HIDReportError ("CFNumberGetValue error retrieving pDevice->usagePage."); refCF = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDPrimaryUsageKey)); if (refCF) if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->usage)) HIDReportError ("CFNumberGetValue error retrieving pDevice->usage."); } if (NULL == refCF) // get top level element HID usage page or usage { // use top level element instead CFTypeRef refCFTopElement = 0; refCFTopElement = CFDictionaryGetValue (hidProperties, CFSTR(kIOHIDElementKey)); { // refCFTopElement points to an array of element dictionaries CFRange range = {0, CFArrayGetCount (refCFTopElement)}; CFArrayApplyFunction (refCFTopElement, range, hid_TopLevelElementHandler, NULL); } } } else HIDReportError ("IORegistryEntryCreateCFProperties failed to create usbProperties."); CFRelease (usbProperties); if (kIOReturnSuccess != IOObjectRelease (parent2)) HIDReportError ("IOObjectRelease error with parent2."); if (kIOReturnSuccess != IOObjectRelease (parent1)) HIDReportError ("IOObjectRelease error with parent1."); } } static Boolean hid_MatchElementTypeMask (IOHIDElementType type, HIDElementTypeMask typeMask) { if (typeMask & kHIDElementTypeInput) if ((type == kIOHIDElementTypeInput_Misc) || (type == kIOHIDElementTypeInput_Button) || (type == kIOHIDElementTypeInput_Axis) || (type == kIOHIDElementTypeInput_ScanCodes)) return true; if (typeMask & kHIDElementTypeOutput) if (type == kIOHIDElementTypeOutput) return true; if (typeMask & kHIDElementTypeFeature) if (type == kIOHIDElementTypeFeature) return true; if (typeMask & kHIDElementTypeCollection) if (type == kIOHIDElementTypeCollection) return true; return false; } static pRecElement hid_GetDeviceElement (pRecElement pElement, HIDElementTypeMask typeMask) { // we are asking for this element if (NULL != pElement) { if (hid_MatchElementTypeMask (pElement->type, typeMask)) // if the type match what we are looking for return pElement; // return the element else return HIDGetNextDeviceElement (pElement, typeMask); // else get the next one } return NULL; } static unsigned long HIDCloseReleaseInterface (pRecDevice pDevice) { IOReturn result = kIOReturnSuccess; if (HIDIsValidDevice(pDevice) && (NULL != pDevice->interface)) { // close the interface result = (*(IOHIDDeviceInterface**) pDevice->interface)->close (pDevice->interface); if (kIOReturnNotOpen == result) { // do nothing as device was not opened, thus can't be closed } else if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("HIDCloseReleaseInterface - Failed to close IOHIDDeviceInterface.", result); //release the interface result = (*(IOHIDDeviceInterface**) pDevice->interface)->Release (pDevice->interface); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("HIDCloseReleaseInterface - Failed to release interface.", result); pDevice->interface = NULL; } return result; } // --------------------------------- // count number of devices in global device list (gpDeviceList) static UInt32 hid_CountCurrentDevices (void) { pRecDevice pDevice = gpDeviceList; UInt32 devices = 0; while (pDevice) { devices++; pDevice = pDevice->pNext; } return devices; } static UInt32 HIDCountDevices (void) { gNumDevices = hid_CountCurrentDevices (); return gNumDevices; } static void hid_DisposeDeviceElements (pRecElement pElement) { if (pElement) { if (pElement->pChild) hid_DisposeDeviceElements (pElement->pChild); if (pElement->pSibling) hid_DisposeDeviceElements (pElement->pSibling); free (pElement); } } static pRecDevice hid_DisposeDevice (pRecDevice pDevice) { kern_return_t result = KERN_SUCCESS; pRecDevice pDeviceNext = NULL; if (HIDIsValidDevice(pDevice)) { // save next device prior to disposing of this device pDeviceNext = pDevice->pNext; result = HIDDequeueDevice (pDevice); #if 0 if (kIOReturnSuccess != result) HIDReportErrorNum ("hid_DisposeDevice: HIDDequeueDevice error: 0x%8.8X.", result); #endif 1 hid_DisposeDeviceElements (pDevice->pListElements); pDevice->pListElements = NULL; result = HIDCloseReleaseInterface (pDevice); // function sanity checks interface value (now application does not own device) if (kIOReturnSuccess != result) HIDReportErrorNum ("hid_DisposeDevice: HIDCloseReleaseInterface error: 0x%8.8X.", result); #if USE_NOTIFICATIONS if (pDevice->interface) { // replace (*pDevice->interface)->Release(pDevice->interface); result = IODestroyPlugInInterface (pDevice->interface); if (kIOReturnSuccess != result) HIDReportErrorNum ("hid_DisposeDevice: IODestroyPlugInInterface error: 0x%8.8X.", result); } if (pDevice->notification) { result = IOObjectRelease((io_object_t) pDevice->notification); if (kIOReturnSuccess != result) HIDReportErrorNum ("hid_DisposeDevice: IOObjectRelease error: 0x%8.8X.", result); } #endif USE_NOTIFICATIONS // remove this device from the device list if (gpDeviceList == pDevice) // head of list? gpDeviceList = pDeviceNext; else { pRecDevice pDeviceTemp = pDeviceNext = gpDeviceList; // we're going to return this if we don't find ourselfs in the list while (pDeviceTemp) { if (pDeviceTemp->pNext == pDevice) // found us! { // take us out of linked list pDeviceTemp->pNext = pDeviceNext = pDevice->pNext; break; } pDeviceTemp = pDeviceTemp->pNext; } } free (pDevice); } // update device count gNumDevices = hid_CountCurrentDevices (); return pDeviceNext; } // --------------------------------- // disposes and releases queue, sets queue to NULL,. // Note: will have no effect if device or queue do not exist static IOReturn hid_DisposeReleaseQueue (pRecDevice pDevice) { IOReturn result = kIOReturnError; // assume failure (pessimist!) if (HIDIsValidDevice(pDevice)) // need valid device { if (pDevice->queue) // and queue { // stop queue result = (*(IOHIDQueueInterface**) pDevice->queue)->stop (pDevice->queue); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("hid_DisposeReleaseQueue - Failed to stop queue.", result); // dispose of queue result = (*(IOHIDQueueInterface**) pDevice->queue)->dispose (pDevice->queue); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("hid_DisposeReleaseQueue - Failed to dipose queue.", result); // release the queue result = (*(IOHIDQueueInterface**) pDevice->queue)->Release (pDevice->queue); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("hid_DisposeReleaseQueue - Failed to release queue.", result); pDevice->queue = NULL; } else HIDREPORTERROR ("hid_DisposeReleaseQueue - no queue."); } else HIDREPORTERROR ("hid_DisposeReleaseQueue - Invalid device."); return result; } // --------------------------------- // completely removes all elements from queue and releases queue and closes device interface // does not release device interfaces, application must call HIDReleaseDeviceList on exit static unsigned long HIDDequeueDevice (pRecDevice pDevice) { IOReturn result = kIOReturnSuccess; if (HIDIsValidDevice(pDevice)) { if ((pDevice->interface) && (pDevice->queue)) { // iterate through elements and if queued, remove pRecElement pElement = HIDGetFirstDeviceElement (pDevice, kHIDElementTypeIO); while (pElement) { if ((*(IOHIDQueueInterface**) pDevice->queue)->hasElement (pDevice->queue, pElement->cookie)) { result = (*(IOHIDQueueInterface**) pDevice->queue)->removeElement (pDevice->queue, pElement->cookie); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("HIDDequeueDevice - Failed to remove element from queue.", result); } pElement = HIDGetNextDeviceElement (pElement, kHIDElementTypeIO); } } // ensure queue is disposed and released // interface will be closed and released on call to HIDReleaseDeviceList result = hid_DisposeReleaseQueue (pDevice); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("removeElement - Failed to dispose and release queue.", result); #if USE_ASYNC_EVENTS else if (NULL != pDevice->queueRunLoopSource) { if (CFRunLoopContainsSource(CFRunLoopGetCurrent(), pDevice->queueRunLoopSource, kCFRunLoopDefaultMode)) CFRunLoopRemoveSource(CFRunLoopGetCurrent(), pDevice->queueRunLoopSource, kCFRunLoopDefaultMode); CFRelease(pDevice->queueRunLoopSource); pDevice->queueRunLoopSource = NULL; } #endif USE_ASYNC_EVENTS } else { HIDREPORTERROR ("HIDDequeueDevice - Invalid device."); result = kIOReturnBadArgument; } return result; } // --------------------------------- // releases all device queues for quit or rebuild (must be called) // does not release device interfaces, application must call HIDReleaseDeviceList on exit static unsigned long HIDReleaseAllDeviceQueues (void) { IOReturn result = kIOReturnBadArgument; pRecDevice pDevice = HIDGetFirstDevice (); while (pDevice) { result = HIDDequeueDevice (pDevice); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("HIDReleaseAllDeviceQueues - Could not dequeue device.", result); pDevice = HIDGetNextDevice (pDevice); } return result; } // --------------------------------- // Get the next event in the queue for a device // elements or entire device should be queued prior to calling this with HIDQueueElement or HIDQueueDevice // returns true if an event is avialable for the element and fills out *pHIDEvent structure, returns false otherwise // Note: kIOReturnUnderrun returned from getNextEvent indicates an empty queue not an error condition // Note: application should pass in a pointer to a IOHIDEventStruct cast to a void (for CFM compatibility) static unsigned char HIDGetEvent (pRecDevice pDevice, void * pHIDEvent) { IOReturn result = kIOReturnBadArgument; AbsoluteTime zeroTime = {0,0}; if (HIDIsValidDevice(pDevice)) { if (pDevice->queue) { result = (*(IOHIDQueueInterface**) pDevice->queue)->getNextEvent (pDevice->queue, (IOHIDEventStruct *)pHIDEvent, zeroTime, 0); if (kIOReturnUnderrun == result) return false; // no events in queue not an error per say else if (kIOReturnSuccess != result) // actual error versus just an empty queue HIDREPORTERRORNUM ("HIDGetEvent - Could not get HID event via getNextEvent.", result); else return true; } else HIDREPORTERROR ("HIDGetEvent - queue does not exist."); } else HIDREPORTERROR ("HIDGetEvent - invalid device."); return false; // did not get event } static unsigned long HIDCreateOpenDeviceInterface (UInt32 hidDevice, pRecDevice pDevice) { IOReturn result = kIOReturnSuccess; HRESULT plugInResult = S_OK; SInt32 score = 0; IOCFPlugInInterface ** ppPlugInInterface = NULL; if (NULL == pDevice->interface) { result = IOCreatePlugInInterfaceForService (hidDevice, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &ppPlugInInterface, &score); if (kIOReturnSuccess == result) { // Call a method of the intermediate plug-in to create the device interface plugInResult = (*ppPlugInInterface)->QueryInterface (ppPlugInInterface, CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID), (void *) &(pDevice->interface)); if (S_OK != plugInResult) HIDReportErrorNum ("CouldnÕt query HID class device interface from plugInInterface", plugInResult); IODestroyPlugInInterface (ppPlugInInterface); // replace (*ppPlugInInterface)->Release (ppPlugInInterface) } else HIDReportErrorNum ("Failed to create **plugInInterface via IOCreatePlugInInterfaceForService.", result); } if (NULL != pDevice->interface) { result = (*(IOHIDDeviceInterface**)pDevice->interface)->open (pDevice->interface, 0); if (kIOReturnSuccess != result) HIDReportErrorNum ("Failed to open pDevice->interface via open.", result); } return result; } // --------------------------------- // adds device to linked list of devices passed in (handles NULL lists properly) // (returns where you just stored it) static pRecDevice* hid_AddDevice (pRecDevice *ppListDeviceHead, pRecDevice pNewDevice) { pRecDevice* result = NULL; if (NULL == *ppListDeviceHead) result = ppListDeviceHead; else { pRecDevice pDevicePrevious = NULL, pDevice = *ppListDeviceHead; while (pDevice) { pDevicePrevious = pDevice; pDevice = pDevicePrevious->pNext; } result = &pDevicePrevious->pNext; } pNewDevice->pNext = NULL; *result = pNewDevice; return result; } static pRecDevice hid_BuildDevice (io_object_t hidDevice) { pRecDevice pDevice = (pRecDevice) malloc (sizeof (recDevice)); if (NULL != pDevice) { // get dictionary for HID properties CFMutableDictionaryRef hidProperties = 0; kern_return_t result = IORegistryEntryCreateCFProperties (hidDevice, &hidProperties, kCFAllocatorDefault, kNilOptions); // clear record bzero(pDevice, sizeof(recDevice)); if ((result == KERN_SUCCESS) && (NULL != hidProperties)) { pRecElement pCurrentElement = NULL; // create device interface result = HIDCreateOpenDeviceInterface (hidDevice, pDevice); if (kIOReturnSuccess != result) HIDReportErrorNum ("HIDCreateOpenDeviceInterface failed.", result); hid_GetDeviceInfo (hidDevice, hidProperties, pDevice); // hidDevice used to find parents in registry tree // set current device for use in getting elements gCurrentGetDevice = pDevice; // Add all elements hid_GetCollectionElements (hidProperties, &pCurrentElement); gCurrentGetDevice = NULL; CFRelease (hidProperties); } else HIDReportErrorNum ("IORegistryEntryCreateCFProperties error when creating deviceProperties.", result); } else HIDReportError ("malloc error when allocating pRecDevice."); return pDevice; } #if USE_NOTIFICATIONS //================================================================================================ // // hid_DeviceNotification // // This routine will get called whenever any kIOGeneralInterest notification happens. We are // interested in the kIOMessageServiceIsTerminated message so that's what we look for. Other // messages are defined in IOMessage.h. // //================================================================================================ // static void hid_DeviceNotification( void *refCon, io_service_t service, natural_t messageType, void *messageArgument ) { pRecDevice pDevice = (pRecDevice) refCon; if (messageType == kIOMessageServiceIsTerminated) { //printf("Device 0x%08x \"%s\"removed.\n", service, pDevice->product); // ryan added this. if (pDevice->disconnect == DISCONNECT_CONNECTED) pDevice->disconnect = DISCONNECT_TELLUSER; // Free the data we're no longer using now that the device is going away // ryan commented this out. //hid_DisposeDevice (pDevice); } } #else static void hid_RemovalCallbackFunction(void * target, IOReturn result, void * refcon, void * sender) { // ryan commented this out. //hid_DisposeDevice ((pRecDevice) target); // ryan added this. pRecDevice = (pRecDevice) target; if (pDevice->disconnect == DISCONNECT_CONNECTED) pDevice->disconnect = DISCONNECT_TELLUSER; } #endif USE_NOTIFICATIONS static void hid_AddDevices (void *refCon, io_iterator_t iterator) { // NOTE: refcon passed in is used to point to the device list head pRecDevice* pListDeviceHead = (pRecDevice*) refCon; IOReturn result = kIOReturnSuccess; io_object_t ioHIDDeviceObject = 0; while ((ioHIDDeviceObject = IOIteratorNext (iterator)) != 0) { pRecDevice* pNewDeviceAt = NULL; pRecDevice pNewDevice = hid_BuildDevice (ioHIDDeviceObject); if (pNewDevice) { #if 0 // set true for verbose output printf("\nhid_AddDevices: pNewDevice = {t: \"%s\", v: %ld, p: %ld, v: %ld, m: \"%s\", " \ "p: \"%s\", l: %ld, u: %4.4lX:%4.4lX, #e: %ld, #f: %ld, #i: %ld, #o: %ld, " \ "#c: %ld, #a: %ld, #b: %ld, #h: %ld, #s: %ld, #d: %ld, #w: %ld}.", pNewDevice->transport, pNewDevice->vendorID, pNewDevice->productID, pNewDevice->version, pNewDevice->manufacturer, pNewDevice->product, pNewDevice->locID, pNewDevice->usagePage, pNewDevice->usage, pNewDevice->totalElements, pNewDevice->features, pNewDevice->inputs, pNewDevice->outputs, pNewDevice->collections, pNewDevice->axis, pNewDevice->buttons, pNewDevice->hats, pNewDevice->sliders, pNewDevice->dials, pNewDevice->wheels ); fflush(stdout); #elif 0 // otherwise output brief description printf("\nhid_AddDevices: pNewDevice = {m: \"%s\" p: \"%s\", vid: %ld, pid: %ld, loc: %8.8lX, usage: %4.4lX:%4.4lX}.", pNewDevice->manufacturer, pNewDevice->product, pNewDevice->vendorID, pNewDevice->productID, pNewDevice->locID, pNewDevice->usagePage, pNewDevice->usage ); fflush(stdout); #endif pNewDeviceAt = hid_AddDevice (pListDeviceHead, pNewDevice); } #if USE_NOTIFICATIONS // Register for an interest notification of this device being removed. Use a reference to our // private data as the refCon which will be passed to the notification callback. result = IOServiceAddInterestNotification( gNotifyPort, // notifyPort ioHIDDeviceObject, // service kIOGeneralInterest, // interestType hid_DeviceNotification, // callback pNewDevice, // refCon (io_object_t*) &pNewDevice->notification); // notification if (KERN_SUCCESS != result) HIDReportErrorNum ("hid_AddDevices: IOServiceAddInterestNotification error: x0%8.8lX.", result); #else result = (*(IOHIDDeviceInterface**)pNewDevice->interface)->setRemovalCallback (pNewDevice->interface, hid_RemovalCallbackFunction,pNewDeviceAt,0); #endif USE_NOTIFICATIONS // release the device object, it is no longer needed result = IOObjectRelease (ioHIDDeviceObject); if (KERN_SUCCESS != result) HIDReportErrorNum ("hid_AddDevices: IOObjectRelease error with ioHIDDeviceObject.", result); } } static Boolean HIDBuildDeviceList (UInt32 usagePage, UInt32 usage) { IOReturn result = kIOReturnSuccess; mach_port_t masterPort = 0; if (NULL != gpDeviceList) HIDReleaseDeviceList (); result = IOMasterPort (bootstrap_port, &masterPort); if (kIOReturnSuccess != result) HIDReportErrorNum ("IOMasterPort error with bootstrap_port.", result); else { CFMutableDictionaryRef hidMatchDictionary = NULL; // Set up matching dictionary to search the I/O Registry for HID devices we are interested in. Dictionary reference is NULL if error. { CFNumberRef refUsage = NULL, refUsagePage = NULL; // Set up a matching dictionary to search I/O Registry by class name for all HID class devices. hidMatchDictionary = IOServiceMatching (kIOHIDDeviceKey); if (NULL != hidMatchDictionary) { if (usagePage) { // Add key for device type (joystick, in this case) to refine the matching dictionary. refUsagePage = CFNumberCreate (kCFAllocatorDefault, kCFNumberLongType, &usagePage); CFDictionarySetValue (hidMatchDictionary, CFSTR (kIOHIDPrimaryUsagePageKey), refUsagePage); CFRelease (refUsagePage); if (usage) { refUsage = CFNumberCreate (kCFAllocatorDefault, kCFNumberLongType, &usage); CFDictionarySetValue (hidMatchDictionary, CFSTR (kIOHIDPrimaryUsageKey), refUsage); CFRelease (refUsage); } } CFRetain(hidMatchDictionary); } else HIDReportError ("Failed to get HID CFMutableDictionaryRef via IOServiceMatching."); } #if USE_NOTIFICATIONS // Create a notification port and add its run loop event source to our run loop // This is how async notifications get set up. { CFRunLoopSourceRef runLoopSource; gNotifyPort = IONotificationPortCreate(masterPort); runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort); gRunLoop = CFRunLoopGetCurrent(); CFRunLoopAddSource(gRunLoop, runLoopSource, kCFRunLoopDefaultMode); // Now set up a notification to be called when a device is first matched by I/O Kit. result = IOServiceAddMatchingNotification(gNotifyPort, // notifyPort kIOFirstMatchNotification, // notificationType hidMatchDictionary, // matching hid_AddDevices, // callback &gpDeviceList, // refCon &gAddedIter // notification ); // call it now to add all existing devices hid_AddDevices(&gpDeviceList,gAddedIter); return true; } #else { io_iterator_t hidObjectIterator = NULL; // Now search I/O Registry for matching devices. result = IOServiceGetMatchingServices (masterPort, hidMatchDictionary, &hidObjectIterator); if (kIOReturnSuccess != result) HIDReportErrorNum ("Failed to create IO object iterator, error:", result); else if (NULL == hidObjectIterator) // likely no HID devices which matched selection criteria are connected HIDReportError ("Warning: Could not find any matching devices, thus iterator creation failed."); if (NULL != hidObjectIterator) { hid_AddDevices(&gpDeviceList,hidObjectIterator); result = IOObjectRelease (hidObjectIterator); // release the iterator if (kIOReturnSuccess != result) HIDReportErrorNum ("IOObjectRelease error with hidObjectIterator.", result); gNumDevices = hid_CountCurrentDevices (); return true; } } #endif USE_NOTIFICATIONS // IOServiceGetMatchingServices consumes a reference to the dictionary, so we don't need to release the dictionary ref. hidMatchDictionary = NULL; } return false; } // --------------------------------- // release list built by above function // MUST be called prior to application exit to properly release devices // if not called (or app crashes) devices can be recovered by pluging into different location in USB chain static void HIDReleaseDeviceList (void) { while (NULL != gpDeviceList) gpDeviceList = hid_DisposeDevice (gpDeviceList); // dispose current device return next device will set gpDeviceList to NULL gNumDevices = 0; } // --------------------------------- // get the first device in the device list // returns NULL if no list exists static pRecDevice HIDGetFirstDevice (void) { return gpDeviceList; } // --------------------------------- // get next device in list given current device as parameter // returns NULL if end of list static pRecDevice HIDGetNextDevice (pRecDevice pDevice) { if (NULL != pDevice) return pDevice->pNext; else return NULL; } // --------------------------------- // get the first element of device passed in as parameter // returns NULL if no list exists or device does not exists or is NULL static pRecElement HIDGetFirstDeviceElement (pRecDevice pDevice, HIDElementTypeMask typeMask) { if (HIDIsValidDevice(pDevice)) { if (hid_MatchElementTypeMask (pDevice->pListElements->type, typeMask)) // ensure first type matches return pDevice->pListElements; else return HIDGetNextDeviceElement (pDevice->pListElements, typeMask); } else return NULL; } // --------------------------------- // get next element of given device in list given current element as parameter // will walk down each collection then to next element or collection (depthwise traverse) // returns NULL if end of list // uses mask of HIDElementTypeMask to restrict element found // use kHIDElementTypeIO to get previous HIDGetNextDeviceElement functionality static pRecElement HIDGetNextDeviceElement (pRecElement pElement, HIDElementTypeMask typeMask) { // should only have elements passed in (though someone could mix calls and pass us a collection) // collection means return the next child or sibling (in that order) // element means returnt he next sibling (as elements can't have children if (NULL != pElement) { if (pElement->pChild) { if (pElement->type != kIOHIDElementTypeCollection) HIDReportError ("Malformed element list: found child of element."); else return hid_GetDeviceElement (pElement->pChild, typeMask); // return the child of this element } else if (pElement->pSibling) { return hid_GetDeviceElement (pElement->pSibling, typeMask); //return the sibling of this element } else // at end back up correctly { pRecElement pPreviousElement = NULL; // malformed device ending in collection if (pElement->type == kIOHIDElementTypeCollection) HIDReportError ("Malformed device: found collection at end of element chain."); // walk back up tree to element prior to first collection ecountered and take next element while (NULL != pElement->pPrevious) { pPreviousElement = pElement; pElement = pElement->pPrevious; // look at previous element // if we have a collection and the previous element is the branch element (should have both a colection and next element attached to it) // if we found a collection, which we are not at the sibling level that actually does have siblings if (((pElement->type == kIOHIDElementTypeCollection) && (pPreviousElement != pElement->pSibling) && pElement->pSibling) || // or if we are at the top (NULL == pElement->pPrevious)) // at top of tree break; } if (NULL == pElement->pPrevious) return NULL; // got to top of list with only a collection as the first element // now we must have been down the child route so go down the sibling route pElement = pElement->pSibling; // element of interest return hid_GetDeviceElement (pElement, typeMask); // otherwise return this element } } return NULL; } // return true if this is a valid device pointer Boolean HIDIsValidDevice(const pRecDevice pSearchDevice) { pRecDevice pDevice = gpDeviceList; while (pDevice) { if (pDevice == pSearchDevice) return true; pDevice = pDevice->pNext; } return false; } static IOReturn hid_CreateQueue (pRecDevice pDevice) { IOReturn result = kIOReturnError; // assume failure (pessimist!) if (HIDIsValidDevice(pDevice)) { if (NULL == pDevice->queue) // do we already have a queue { if (NULL != pDevice->interface) { pDevice->queue = (void *) (*(IOHIDDeviceInterface**) pDevice->interface)->allocQueue (pDevice->interface); // alloc queue if (pDevice->queue) { result = (*(IOHIDQueueInterface**) pDevice->queue)->create (pDevice->queue, 0, kDeviceQueueSize); // create actual queue if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("hid_CreateQueue - Failed to create queue via create", result); } else { HIDREPORTERROR ("hid_CreateQueue - Failed to alloc IOHIDQueueInterface ** via allocQueue"); result = kIOReturnError; // synthesis error } } else HIDREPORTERRORNUM ("hid_CreateQueue - Device inteface does not exist for queue creation", result); } } else HIDREPORTERRORNUM ("hid_CreateQueue - Invalid Device", result); return result; } static unsigned long HIDQueueDevice (pRecDevice pDevice) { IOReturn result = kIOReturnError; // assume failure (pessimist!) pRecElement pElement; if (HIDIsValidDevice(pDevice)) { // error checking if (NULL == pDevice) { HIDREPORTERROR ("HIDQueueDevice - Device does not exist."); return kIOReturnBadArgument; } if (NULL == pDevice->interface) // must have interface { HIDREPORTERROR ("HIDQueueDevice - Device does not have interface."); return kIOReturnError; } if (NULL == pDevice->queue) // if no queue create queue result = hid_CreateQueue (pDevice); if ((kIOReturnSuccess != result) || (NULL == pDevice->queue)) { HIDREPORTERRORNUM ("HIDQueueDevice - problem creating queue.", result); if (kIOReturnSuccess != result) return result; else return kIOReturnError; } // stop queue result = (*(IOHIDQueueInterface**) pDevice->queue)->stop (pDevice->queue); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("HIDQueueDevice - Failed to stop queue.", result); // queue element //¥ pElement = HIDGetFirstDeviceElement (pDevice, kHIDElementTypeIO); pElement = HIDGetFirstDeviceElement (pDevice, kHIDElementTypeInput | kHIDElementTypeFeature); while (pElement) { if (!(*(IOHIDQueueInterface**) pDevice->queue)->hasElement (pDevice->queue, pElement->cookie)) { result = (*(IOHIDQueueInterface**) pDevice->queue)->addElement (pDevice->queue, pElement->cookie, 0); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("HIDQueueDevice - Failed to add element to queue.", result); } //¥ pElement = HIDGetNextDeviceElement (pElement, kHIDElementTypeIO); pElement = HIDGetNextDeviceElement (pElement, kHIDElementTypeInput | kHIDElementTypeFeature); } // start queue result = (*(IOHIDQueueInterface**) pDevice->queue)->start (pDevice->queue); if (kIOReturnSuccess != result) HIDREPORTERRORNUM ("HIDQueueDevice - Failed to start queue.", result); } else HIDREPORTERROR ("HIDQueueDevice - Invalid device."); return result; } /* -- END HID UTILITIES -- */ static int available_mice = 0; static pRecDevice *devices = NULL; /* returns non-zero if (a <= b). */ typedef unsigned long long ui64; static inline int oldEvent(const AbsoluteTime *a, const AbsoluteTime *b) { #if 0 // !!! FIXME: doesn't work, timestamps aren't reliable. const ui64 a64 = (((unsigned long long) a->hi) << 32) | a->lo; const ui64 b64 = (((unsigned long long) b->hi) << 32) | b->lo; #endif return 0; } /* oldEvent */ static int poll_mouse(pRecDevice mouse, ManyMouseEvent *outevent) { int unhandled = 1; while (unhandled) /* read until failure or valid event. */ { pRecElement recelem; IOHIDEventStruct event; if (!HIDGetEvent(mouse, &event)) return(0); /* no new event. */ unhandled = 0; /* will reset if necessary. */ recelem = HIDGetFirstDeviceElement(mouse, kHIDElementTypeInput); while (recelem != NULL) { if (recelem->cookie == event.elementCookie) break; recelem = HIDGetNextDeviceElement(recelem, kHIDElementTypeInput); } /* while */ if (recelem == NULL) continue; /* unknown device element. Can this actually happen? */ outevent->value = event.value; if (recelem->usagePage == kHIDPage_GenericDesktop) { /* * some devices (two-finger-scroll trackpads?) seem to give * a flood of events with values of zero for every legitimate * event. Throw these zero events out. */ if (outevent->value == 0) unhandled = 1; else { switch (recelem->usage) { case kHIDUsage_GD_X: case kHIDUsage_GD_Y: if (oldEvent(&event.timestamp, &mouse->lastScrollTime)) unhandled = 1; else { outevent->type = MANYMOUSE_EVENT_RELMOTION; if (recelem->usage == kHIDUsage_GD_X) outevent->item = 0; else outevent->item = 1; } /* else */ break; case kHIDUsage_GD_Wheel: memcpy(&mouse->lastScrollTime, &event.timestamp, sizeof (AbsoluteTime)); outevent->type = MANYMOUSE_EVENT_SCROLL; outevent->item = 0; /* !!! FIXME: horiz scroll? */ break; default: /* !!! FIXME: absolute motion? */ unhandled = 1; } /* switch */ } /* else */ } /* if */ else if (recelem->usagePage == kHIDPage_Button) { outevent->type = MANYMOUSE_EVENT_BUTTON; outevent->item = ((int) recelem->usage) - 1; } /* else if */ else { unhandled = 1; } /* else */ } /* while */ return(1); /* got a valid event */ } /* poll_mouse */ static void macosx_hidutilities_quit(void) { HIDReleaseAllDeviceQueues(); HIDReleaseDeviceList(); free(devices); devices = NULL; available_mice = 0; } /* macosx_hidutilities_quit */ static int macosx_hidutilities_init(void) { macosx_hidutilities_quit(); /* just in case... */ if (!HIDBuildDeviceList(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse)) return(-1); available_mice = HIDCountDevices(); if (available_mice > 0) { int i; pRecDevice dev = NULL; dev = HIDGetFirstDevice(); devices = (pRecDevice *) malloc(sizeof (pRecDevice) * available_mice); if ((devices == NULL) || (dev == NULL)) { macosx_hidutilities_quit(); return(-1); } /* if */ for (i = 0; i < available_mice; i++) { if (dev == NULL) /* what? list ended? Truncate final list... */ available_mice = i; if (HIDQueueDevice(dev) == kIOReturnSuccess) devices[i] = dev; else /* failed? Chop this device from the list... */ { i--; available_mice--; } /* else */ dev = HIDGetNextDevice(dev); } /* for */ } /* if */ return(available_mice); } /* macosx_hidutilities_init */ static const char *macosx_hidutilities_name(unsigned int index) { if (index >= available_mice) return(NULL); return((const char *) devices[index]->product); } /* macosx_hidutilities_name */ static int macosx_hidutilities_poll(ManyMouseEvent *event) { /* * (i) is static so we iterate through all mice round-robin. This * prevents a chatty mouse from dominating the queue. */ static unsigned int i = 0; if (i >= available_mice) i = 0; /* handle reset condition. */ if (event != NULL) { while (i < available_mice) { pRecDevice dev = devices[i]; if ((dev) && (dev->disconnect != DISCONNECT_COMPLETE)) { event->device = i; /* see if mouse was unplugged since last polling... */ if (dev->disconnect == DISCONNECT_TELLUSER) { dev->disconnect = DISCONNECT_COMPLETE; event->type = MANYMOUSE_EVENT_DISCONNECT; return(1); } /* if */ if (poll_mouse(dev, event)) return(1); } /* if */ i++; } /* while */ } /* if */ return(0); /* no new events */ } /* macosx_hidutilities_poll */ static const ManyMouseDriver ManyMouseDriver_interface = { macosx_hidutilities_init, macosx_hidutilities_quit, macosx_hidutilities_name, macosx_hidutilities_poll }; const ManyMouseDriver *ManyMouseDriver_hidutilities = &ManyMouseDriver_interface; #else const ManyMouseDriver *ManyMouseDriver_hidutilities = 0; #endif /* ifdef Mac OS X blocker */ /* end of macosx_hidutilities.c ... */