aboutsummaryrefslogtreecommitdiffstats
path: root/lib/DDHidDevice.m
diff options
context:
space:
mode:
Diffstat (limited to 'lib/DDHidDevice.m')
-rw-r--r--lib/DDHidDevice.m585
1 files changed, 585 insertions, 0 deletions
diff --git a/lib/DDHidDevice.m b/lib/DDHidDevice.m
new file mode 100644
index 0000000..8000185
--- /dev/null
+++ b/lib/DDHidDevice.m
@@ -0,0 +1,585 @@
+/*
+ * Copyright (c) 2007 Dave Dribin
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#import "DDHidDevice.h"
+#import "DDHidUsage.h"
+#import "DDHidElement.h"
+#import "DDHidQueue.h"
+#import "NSDictionary+DDHidExtras.h"
+#import "NSXReturnThrowError.h"
+
+#include <IOKit/hid/IOHIDUsageTables.h>
+
+@interface DDHidDevice (Private)
+
++ (void) addDevice: (io_object_t) hidDevice
+ withClass: (Class) hidClass
+ skipZeroLocations: (BOOL) skipZeroLocations
+ toDeviceList: (NSMutableArray *) devices;
+
+- (BOOL) initPropertiesWithError: (NSError **) error_;
+- (BOOL) createDeviceInterfaceWithError: (NSError **) error_;
+
+@end
+
+@implementation DDHidDevice
+
+- (id) initWithDevice: (io_object_t) device error: (NSError **) error;
+{
+ return [self initLogicalWithDevice: device
+ logicalDeviceNumber: 0
+ error: error];
+}
+
+- (id) initLogicalWithDevice: (io_object_t) device
+ logicalDeviceNumber: (int) logicalDeviceNumber
+ error: (NSError **) error;
+{
+ self = [super init];
+ if (self == nil)
+ return nil;
+
+ mHidDevice = device;
+ IOObjectRetain(mHidDevice);
+
+ if (![self initPropertiesWithError: error])
+ {
+ [self release];
+ return nil;
+ }
+
+ if (![self createDeviceInterfaceWithError: error])
+ {
+ [self release];
+ return nil;
+ }
+
+ mLogicalDeviceNumber = logicalDeviceNumber;
+ mListenInExclusiveMode = NO;
+ mDefaultQueue = nil;
+ mTag = 0;
+
+ return self;
+}
+
+//===========================================================
+// dealloc
+//===========================================================
+- (void) dealloc
+{
+ [mDefaultQueue release];
+ if (mDeviceInterface != NULL)
+ {
+ (*mDeviceInterface)->close(mDeviceInterface);
+ (*mDeviceInterface)->Release(mDeviceInterface);
+ }
+ [mElementsByCookie release];
+ [mElements release];
+ [mUsages release];
+ [mPrimaryUsage release];
+ [mProperties release];
+ IOObjectRelease(mHidDevice);
+
+ mProperties = nil;
+ mDeviceInterface = NULL;
+ [super dealloc];
+}
+
+#pragma mark -
+#pragma mark Finding Devices
+
++ (NSArray *) allDevices;
+{
+ // Set up a matching dictionary to search the I/O Registry by class
+ // name for all HID class devices
+ CFMutableDictionaryRef hidMatchDictionary =
+ IOServiceMatching(kIOHIDDeviceKey);
+ return [self allDevicesMatchingCFDictionary: hidMatchDictionary
+ withClass: [DDHidDevice class]
+ skipZeroLocations: NO];
+}
+
++ (NSArray *) allDevicesMatchingUsagePage: (unsigned) usagePage
+ usageId: (unsigned) usageId
+ withClass: (Class) hidClass
+ skipZeroLocations: (BOOL) skipZeroLocations;
+{
+ // Set up a matching dictionary to search the I/O Registry by class
+ // name for all HID class devices
+ CFMutableDictionaryRef hidMatchDictionary =
+ IOServiceMatching(kIOHIDDeviceKey);
+ NSMutableDictionary * objcMatchDictionary =
+ (NSMutableDictionary *) hidMatchDictionary;
+ [objcMatchDictionary ddhid_setObject: [NSNumber numberWithUnsignedInt: usagePage]
+ forString: kIOHIDDeviceUsagePageKey];
+ [objcMatchDictionary ddhid_setObject: [NSNumber numberWithUnsignedInt: usageId]
+ forString: kIOHIDDeviceUsageKey];
+ return [self allDevicesMatchingCFDictionary: hidMatchDictionary
+ withClass: hidClass
+ skipZeroLocations: skipZeroLocations];
+}
+
++ (NSArray *) allDevicesMatchingCFDictionary: (CFDictionaryRef) matchDictionary
+ withClass: (Class) hidClass
+ skipZeroLocations: (BOOL) skipZeroLocations;
+{
+ // Now search I/O Registry for matching devices.
+ io_iterator_t hidObjectIterator = MACH_PORT_NULL;
+ NSMutableArray * devices = [NSMutableArray array];
+ @try
+ {
+ NSXThrowError(IOServiceGetMatchingServices(kIOMasterPortDefault,
+ matchDictionary,
+ &hidObjectIterator));
+
+ if (hidObjectIterator == 0)
+ return [NSArray array];
+
+ io_object_t hidDevice;
+ while (hidDevice = IOIteratorNext(hidObjectIterator))
+ {
+ [self addDevice: hidDevice
+ withClass: hidClass
+ skipZeroLocations: skipZeroLocations
+ toDeviceList: devices];
+ }
+
+ // This makes sure the array return is consistent from run to run,
+ // assuming no new devices were added.
+ [devices sortUsingSelector: @selector(compareByLocationId:)];
+ }
+ @finally
+ {
+ if (hidObjectIterator != MACH_PORT_NULL)
+ IOObjectRelease(hidObjectIterator);
+ }
+
+ return devices;
+}
+
+- (int) logicalDeviceCount;
+{
+ return 1;
+}
+
+#pragma mark -
+#pragma mark I/O Kit Objects
+
+- (io_object_t) ioDevice;
+{
+ return mHidDevice;
+}
+
+- (IOHIDDeviceInterface122**) deviceInterface;
+{
+ return mDeviceInterface;
+}
+
+#pragma mark -
+#pragma mark Operations
+
+- (void) open;
+{
+ [self openWithOptions: kIOHIDOptionsTypeNone];
+}
+
+- (void) openWithOptions: (UInt32) options;
+{
+ NSXThrowError((*mDeviceInterface)->open(mDeviceInterface, options));
+}
+
+- (void) close;
+{
+ NSXThrowError((*mDeviceInterface)->close(mDeviceInterface));
+}
+
+- (DDHidQueue *) createQueueWithSize: (unsigned) size;
+{
+ IOHIDQueueInterface ** queue =
+ (*mDeviceInterface)->allocQueue(mDeviceInterface);
+ if (queue == NULL)
+ return nil;
+ return [[[DDHidQueue alloc] initWithHIDQueue: queue
+ size: size] autorelease];
+}
+
+- (long) getElementValue: (DDHidElement *) element;
+{
+ IOHIDEventStruct event;
+ NSXThrowError((*mDeviceInterface)->getElementValue(mDeviceInterface,
+ [element cookie],
+ &event));
+ return event.value;
+}
+
+#pragma mark -
+#pragma mark Asynchronous Notification
+
+//===========================================================
+// listenInExclusiveMode
+//===========================================================
+- (BOOL) listenInExclusiveMode
+{
+ return mListenInExclusiveMode;
+}
+
+- (void) setListenInExclusiveMode: (BOOL) flag
+{
+ mListenInExclusiveMode = flag;
+}
+
+- (void) startListening;
+{
+ if ([self isListening])
+ return;
+
+ UInt32 options = kIOHIDOptionsTypeNone;
+ if (mListenInExclusiveMode)
+ options = kIOHIDOptionsTypeSeizeDevice;
+ [self openWithOptions: options];
+ mDefaultQueue = [[self createQueueWithSize: [self sizeOfDefaultQueue]] retain];
+ [mDefaultQueue setDelegate: self];
+ [self addElementsToDefaultQueue];
+ [mDefaultQueue startOnCurrentRunLoop];
+}
+
+- (void) stopListening;
+{
+ if (![self isListening])
+ return;
+
+ [mDefaultQueue stop];
+ [mDefaultQueue release];
+ mDefaultQueue = nil;
+ [self close];
+}
+
+- (BOOL) isListening;
+{
+ return (mDefaultQueue != nil);
+}
+
+#pragma mark -
+#pragma mark Properties
+
+- (NSDictionary *) properties;
+{
+ return mProperties;
+}
+
+//===========================================================
+// - productName
+//===========================================================
+- (NSString *) productName
+{
+ NSString * productName = [mProperties ddhid_stringForString: kIOHIDProductKey];
+ if ([self logicalDeviceCount] > 1)
+ {
+ productName = [productName stringByAppendingString:
+ [NSString stringWithFormat:@" #%d", mLogicalDeviceNumber + 1]];
+ }
+ return productName;
+}
+
+//===========================================================
+// - manufacturer
+//===========================================================
+- (NSString *) manufacturer
+{
+ return [mProperties ddhid_stringForString: kIOHIDManufacturerKey];
+}
+
+//===========================================================
+// - serialNumber
+//===========================================================
+- (NSString *) serialNumber
+{
+ return [mProperties ddhid_stringForString: kIOHIDSerialNumberKey];
+}
+
+//===========================================================
+// - transport
+//===========================================================
+- (NSString *) transport
+{
+ return [mProperties ddhid_stringForString: kIOHIDTransportKey];
+}
+
+//===========================================================
+// - vendorId
+//===========================================================
+- (long) vendorId
+{
+ return [mProperties ddhid_longForString: kIOHIDVendorIDKey];
+}
+
+//===========================================================
+// - productId
+//===========================================================
+- (long) productId
+{
+ return [mProperties ddhid_longForString: kIOHIDProductIDKey];
+}
+
+//===========================================================
+// - version
+//===========================================================
+- (long) version
+{
+ return [mProperties ddhid_longForString: kIOHIDVersionNumberKey];
+}
+
+//===========================================================
+// - locationId
+//===========================================================
+- (long) locationId
+{
+ return [mProperties ddhid_longForString: kIOHIDLocationIDKey];
+}
+
+//===========================================================
+// - usagePage
+//===========================================================
+- (long) usagePage
+{
+ return [mProperties ddhid_longForString: kIOHIDPrimaryUsagePageKey];
+}
+
+//===========================================================
+// - usage
+//===========================================================
+- (long) usage
+{
+ return [mProperties ddhid_longForString: kIOHIDPrimaryUsageKey];
+}
+
+- (NSArray *) elements;
+{
+ return mElements;
+}
+
+- (DDHidElement *) elementForCookie: (IOHIDElementCookie) cookie;
+{
+ NSNumber * n = [NSNumber numberWithUnsignedInt: (unsigned) cookie];
+ return [mElementsByCookie objectForKey: n];
+}
+
+- (DDHidUsage *) primaryUsage;
+{
+ return mPrimaryUsage;
+}
+
+- (NSArray *) usages;
+{
+ return mUsages;
+}
+
+- (NSComparisonResult) compareByLocationId: (DDHidDevice *) device;
+{
+ long myLocationId = [self locationId];
+ long otherLocationId = [device locationId];
+ if (myLocationId < otherLocationId)
+ return NSOrderedAscending;
+ else if (myLocationId > otherLocationId)
+ return NSOrderedDescending;
+ else
+ return NSOrderedSame;
+}
+
+//===========================================================
+// tag
+//===========================================================
+- (int) tag
+{
+ return mTag;
+}
+
+- (void) setTag: (int) theTag
+{
+ mTag = theTag;
+}
+
+@end
+
+@implementation DDHidDevice (Protected)
+
+- (unsigned) sizeOfDefaultQueue;
+{
+ return 10;
+}
+
+- (void) addElementsToDefaultQueue;
+{
+ [mDefaultQueue addElements: [self elements] recursively: YES];
+}
+
+@end
+
+@implementation DDHidDevice (Private)
+
++ (void) addDevice: (io_object_t) hidDevice
+ withClass: (Class) hidClass
+ skipZeroLocations: (BOOL) skipZeroLocations
+ toDeviceList: (NSMutableArray *) devices;
+{
+ @try
+ {
+ NSError * error = nil;
+ DDHidDevice * device = [[hidClass alloc] initWithDevice: hidDevice
+ error: &error];
+ if (device == nil)
+ {
+ NSXRaiseError(error);
+ }
+ [device autorelease];
+
+ if (([device locationId] == 0) && skipZeroLocations)
+ return;
+
+ [devices addObject: device];
+
+ // Add remainnig logical devices
+ int i;
+ for (i = 1; i < [device logicalDeviceCount]; i++)
+ {
+ device = [[hidClass alloc] initLogicalWithDevice: hidDevice
+ logicalDeviceNumber: i
+ error: &error];
+
+ if (device == nil)
+ {
+ NSXRaiseError(error);
+ }
+ [device autorelease];
+
+ [devices addObject: device];
+ }
+ }
+ @finally
+ {
+ IOObjectRelease(hidDevice);
+ }
+}
+
+- (void) indexElements: (NSArray *) elements;
+{
+ NSEnumerator * e = [elements objectEnumerator];
+ DDHidElement * element;
+ while (element = [e nextObject])
+ {
+ NSNumber * n = [NSNumber numberWithUnsignedInt: [element cookieAsUnsigned]];
+ [mElementsByCookie setObject: element
+ forKey: n];
+ NSArray * children = [element elements];
+ if (children != nil)
+ [self indexElements: children];
+ }
+}
+
+- (BOOL) initPropertiesWithError: (NSError **) error_;
+{
+ NSError * error = nil;
+ BOOL result = NO;
+
+ CFMutableDictionaryRef properties;
+ NSXReturnError(IORegistryEntryCreateCFProperties(mHidDevice, &properties,
+ kCFAllocatorDefault, kNilOptions));
+ if (error)
+ goto done;
+
+ mProperties = (NSMutableDictionary *) properties;
+ NSArray * elementProperties = [mProperties ddhid_objectForString: kIOHIDElementKey];
+ mElements = [DDHidElement elementsWithPropertiesArray: elementProperties];
+ [mElements retain];
+
+ unsigned usagePage = [mProperties ddhid_unsignedIntForString: kIOHIDPrimaryUsagePageKey];
+ unsigned usageId = [mProperties ddhid_unsignedIntForString: kIOHIDPrimaryUsageKey];
+
+ mPrimaryUsage = [[DDHidUsage alloc] initWithUsagePage: usagePage
+ usageId: usageId];
+ mUsages = [[NSMutableArray alloc] init];
+
+ NSArray * usagePairs = [mProperties ddhid_objectForString: kIOHIDDeviceUsagePairsKey];
+ NSEnumerator * e = [usagePairs objectEnumerator];
+ NSDictionary * usagePair;
+ while (usagePair = [e nextObject])
+ {
+ usagePage = [usagePair ddhid_unsignedIntForString: kIOHIDDeviceUsagePageKey];
+ usageId = [usagePair ddhid_unsignedIntForString: kIOHIDDeviceUsageKey];
+ DDHidUsage * usage = [DDHidUsage usageWithUsagePage: usagePage
+ usageId: usageId];
+ [mUsages addObject: usage];
+ }
+
+ mElementsByCookie = [[NSMutableDictionary alloc] init];
+ [self indexElements: mElements];
+ result = YES;
+
+done:
+ if (error_)
+ *error_ = error;
+ return result;
+}
+
+- (BOOL) createDeviceInterfaceWithError: (NSError **) error_;
+{
+ io_name_t className;
+ IOCFPlugInInterface ** plugInInterface = NULL;
+ SInt32 score = 0;
+ NSError * error = nil;
+ BOOL result = NO;
+
+ mDeviceInterface = NULL;
+
+ NSXReturnError(IOObjectGetClass(mHidDevice, className));
+ if (error)
+ goto done;
+
+ NSXReturnError(IOCreatePlugInInterfaceForService(mHidDevice,
+ kIOHIDDeviceUserClientTypeID,
+ kIOCFPlugInInterfaceID,
+ &plugInInterface,
+ &score));
+ if (error)
+ goto done;
+
+ //Call a method of the intermediate plug-in to create the device interface
+ NSXReturnError((*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &mDeviceInterface));
+ if (error)
+ goto done;
+
+ result = YES;
+
+done:
+ if (plugInInterface != NULL)
+ {
+ (*plugInInterface)->Release(plugInInterface);
+ }
+ if (error_)
+ *error_ = error;
+ return result;
+}
+
+@end
+