apple2ix/Apple2Mac/DDHidLib/lib/DDHidDevice.m
2016-09-10 11:35:20 -06:00

600 lines
17 KiB
Objective-C

/*
* 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"
#import <objc/message.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);
id retVal = nil;
if(hidMatchDictionary) {
retVal = [self allDevicesMatchingCFDictionary: hidMatchDictionary
withClass: [DDHidDevice class]
skipZeroLocations: NO];
CFRelease(hidMatchDictionary);// free our +1retain to placate static analysis ... (it is also freed by IOServiceGetMatchingServices() )
}
return retVal;
}
+ (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);
id retVal = nil;
if(hidMatchDictionary) {
NSMutableDictionary * objcMatchDictionary =
(NSMutableDictionary *) hidMatchDictionary;
[objcMatchDictionary ddhid_setObject: [NSNumber numberWithUnsignedInt: usagePage]
forString: kIOHIDDeviceUsagePageKey];
[objcMatchDictionary ddhid_setObject: [NSNumber numberWithUnsignedInt: usageId]
forString: kIOHIDDeviceUsageKey];
retVal = [self allDevicesMatchingCFDictionary: hidMatchDictionary
withClass: hidClass
skipZeroLocations: skipZeroLocations];
CFRelease(hidMatchDictionary);// free our +1retain to placate static analysis ... (it is also freed by IOServiceGetMatchingServices() )
}
return retVal;
}
+ (NSArray *) allDevicesMatchingCFDictionary: (CFDictionaryRef) matchDictionary
withClass: (Class) hidClass
skipZeroLocations: (BOOL) skipZeroLocations;
{
(void)((id(*)(id, SEL))objc_msgSend((id)matchDictionary, @selector(retain)));
// 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