diff options
Diffstat (limited to 'tools/EventClients/Clients/OSXRemote/HIDRemote.m')
-rw-r--r-- | tools/EventClients/Clients/OSXRemote/HIDRemote.m | 1544 |
1 files changed, 1544 insertions, 0 deletions
diff --git a/tools/EventClients/Clients/OSXRemote/HIDRemote.m b/tools/EventClients/Clients/OSXRemote/HIDRemote.m new file mode 100644 index 0000000000..f8e9b9f953 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/HIDRemote.m @@ -0,0 +1,1544 @@ +// +// HIDRemote.m +// HIDRemote V1.0 +// +// Created by Felix Schwarz on 06.04.07. +// Copyright 2007-2009 IOSPIRIT GmbH. All rights reserved. +// +// ** LICENSE ************************************************************************* +// +// Copyright (c) 2007-2009 IOSPIRIT GmbH +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list +// of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// * Neither the name of IOSPIRIT GmbH nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. +// +// ************************************************************************************ + +#import "HIDRemote.h" + +// Callback Prototypes +static void HIDEventCallback( void * target, + IOReturn result, + void * refcon, + void * sender); + +static void ServiceMatchingCallback( void *refCon, + io_iterator_t iterator); + +static void ServiceNotificationCallback(void * refCon, + io_service_t service, + natural_t messageType, + void * messageArgument); + +// Shared HIDRemote instance +static HIDRemote *sHIDRemote = nil; + +@implementation HIDRemote + +#pragma mark -- Init, dealloc & shared instance -- + ++ (HIDRemote *)sharedHIDRemote +{ + if (!sHIDRemote) + { + sHIDRemote = [[HIDRemote alloc] init]; + } + + return (sHIDRemote); +} + +- (id)init +{ + if (self = [super init]) + { + // Detect application becoming active/inactive + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationDidBecomeActiveNotification object:[NSApplication sharedApplication]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationWillResignActiveNotification object:[NSApplication sharedApplication]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationWillTerminateNotification object:[NSApplication sharedApplication]]; + + // Handle distributed notifications + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemotePing object:nil]; + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemoteRetry object:[NSString stringWithFormat:@"%d", getpid()]]; + + // Enabled by default: simulate hold events for plus/minus + _simulateHoldEvents = YES; + + // Enabled by default: work around for a locking issue introduced with Security Update 2008-004 / 10.4.9 and beyond + _secureEventInputWorkAround = YES; + _statusSecureEventInputWorkAroundEnabled = NO; + + // Initialize instance variables + _lastSeenRemoteID = -1; + _unusedButtonCodes = [[NSMutableArray alloc] init]; + _exclusiveLockLending = NO; + } + + return (self); +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:[NSApplication sharedApplication]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillResignActiveNotification object:[NSApplication sharedApplication]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidBecomeActiveNotification object:[NSApplication sharedApplication]]; + + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemotePing object:nil]; + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemoteRetry object:[NSString stringWithFormat:@"%d", getpid()]]; + + [self stopRemoteControl]; + + [self setExclusiveLockLendingEnabled:NO]; + + [self setDelegate:nil]; + + [_unusedButtonCodes release]; + _unusedButtonCodes = nil; + + [super dealloc]; +} + +#pragma mark -- PUBLIC: System Information -- ++ (BOOL)isCandelairInstalled +{ + mach_port_t masterPort = 0; + kern_return_t kernResult; + io_service_t matchingService = 0; + BOOL isInstalled = NO; + + kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort); + if (kernResult || !masterPort) { return(NO); } + + if ((matchingService = IOServiceGetMatchingService(masterPort, IOServiceMatching("IOSPIRITIRController"))) != 0) + { + isInstalled = YES; + IOObjectRelease((io_object_t) matchingService); + } + + mach_port_deallocate(mach_task_self(), masterPort); + + return (isInstalled); +} + ++ (BOOL)isCandelairInstallationRequiredForRemoteMode:(HIDRemoteMode)remoteMode +{ + SInt32 systemVersion = 0; + + // Determine OS version + if (Gestalt(gestaltSystemVersion, &systemVersion) == noErr) + { + switch (systemVersion) + { + case 0x1060: // OS 10.6 + case 0x1061: // OS 10.6.1 + // OS X 10.6(.0) and OS X 10.6.1 require the Candelair driver for to be installed, + // so that third party apps can acquire an exclusive lock on the receiver HID Device + // via IOKit. + // + // IMPORTANT: + // We do not include later OS releases here, because the issue may be fixed in these. + // We simply don't know at this point. Updated sourcecode will be available at + // candelair.com as necessary. + + switch (remoteMode) + { + case kHIDRemoteModeExclusive: + case kHIDRemoteModeExclusiveAuto: + if (![self isCandelairInstalled]) + { + return (YES); + } + break; + } + break; + } + } + + return (NO); +} + +#pragma mark -- PUBLIC: Interface / API -- +- (BOOL)startRemoteControl:(HIDRemoteMode)hidRemoteMode +{ + if ((_mode == kHIDRemoteModeNone) && (hidRemoteMode != kHIDRemoteModeNone)) + { + kern_return_t kernReturn; + CFMutableDictionaryRef matchDict=NULL; + + // Work around a locking issue introduced with Security Update 2008-004 / 10.4.9 and beyond + if ((_mode != kHIDRemoteModeShared) && (_secureEventInputWorkAround)) + { + if (!_statusSecureEventInputWorkAroundEnabled) + { + EnableSecureEventInput(); + + // Keep track of our use of EnableSecureEventInput()/DisableSecureEventInput() to avoid count "leaks" + _statusSecureEventInputWorkAroundEnabled = YES; + } + } + + do + { + // Get IOKit master port + kernReturn = IOMasterPort(bootstrap_port, &_masterPort); + if ((kernReturn!=kIOReturnSuccess) || (_masterPort==0)) { break; } + + // Setup notification port + _notifyPort = IONotificationPortCreate(_masterPort); + + if (_notifyRLSource = IONotificationPortGetRunLoopSource(_notifyPort)) + { + CFRunLoopAddSource( CFRunLoopGetCurrent(), + _notifyRLSource, + kCFRunLoopCommonModes); + } + else + { + break; + } + + // Setup notification matching dict + matchDict = IOServiceMatching(kIOHIDDeviceKey); + CFRetain(matchDict); + + // Actually add notification + kernReturn = IOServiceAddMatchingNotification( _notifyPort, + kIOFirstMatchNotification, + matchDict, // one reference count consumed by this call + ServiceMatchingCallback, + (void *) self, + &_matchingServicesIterator); + if (kernReturn != kIOReturnSuccess) { break; } + + // Setup serviceAttribMap + _serviceAttribMap = [[NSMutableDictionary alloc] init]; + if (!_serviceAttribMap) { break; } + + // Phew .. everything went well! + _mode = hidRemoteMode; + CFRelease(matchDict); + + [self _serviceMatching:_matchingServicesIterator]; + + [self _postStatusWithAction:kHIDRemoteDNStatusActionStart]; + + return (YES); + + }while(0); + + // An error occured. Do necessary clean up. + if (matchDict) + { + CFRelease(matchDict); + matchDict = NULL; + } + + [self stopRemoteControl]; + } + + return (NO); +} + +- (void)stopRemoteControl +{ + _autoRecover = NO; + + if (_autoRecoveryTimer) + { + [_autoRecoveryTimer invalidate]; + [_autoRecoveryTimer release]; + _autoRecoveryTimer = nil; + } + + if (_serviceAttribMap) + { + NSDictionary *cloneDict = [[NSDictionary alloc] initWithDictionary:_serviceAttribMap]; + + if (cloneDict) + { + NSEnumerator *mapKeyEnum = [cloneDict keyEnumerator]; + NSNumber *serviceValue; + + while (serviceValue = [mapKeyEnum nextObject]) + { + [self _destructService:(io_object_t)[serviceValue unsignedIntValue]]; + }; + + [cloneDict release]; + cloneDict = nil; + } + + [_serviceAttribMap release]; + _serviceAttribMap = nil; + } + + if (_matchingServicesIterator) + { + IOObjectRelease((io_object_t) _matchingServicesIterator); + _matchingServicesIterator = 0; + } + + if (_notifyRLSource) + { + CFRunLoopSourceInvalidate(_notifyRLSource); + + _notifyRLSource = NULL; + } + + if (_notifyPort) + { + IONotificationPortDestroy(_notifyPort); + _notifyPort = NULL; + } + + if (_masterPort) + { + mach_port_deallocate(mach_task_self(), _masterPort); + } + + if (_statusSecureEventInputWorkAroundEnabled) + { + DisableSecureEventInput(); + _statusSecureEventInputWorkAroundEnabled = NO; + } + + [self _postStatusWithAction:kHIDRemoteDNStatusActionStop]; + + [_returnToPID release]; + _returnToPID = nil; + + _mode = kHIDRemoteModeNone; +} + +- (BOOL)isStarted +{ + return (_mode != kHIDRemoteModeNone); +} + +- (unsigned)activeRemoteControlCount +{ + return ([_serviceAttribMap count]); +} + +- (SInt32)lastSeenRemoteControlID +{ + return (_lastSeenRemoteID); +} + +- (void)setSimulateHoldEvents:(BOOL)newSimulateHoldEvents +{ + _simulateHoldEvents = newSimulateHoldEvents; +} + +- (BOOL)simulateHoldEvents +{ + return (_simulateHoldEvents); +} + +- (NSArray *)unusedButtonCodes +{ + return (_unusedButtonCodes); +} + +- (void)setUnusedButtonCodes:(NSArray *)newArrayWithUnusedButtonCodesAsNSNumbers +{ + [newArrayWithUnusedButtonCodesAsNSNumbers retain]; + [_unusedButtonCodes release]; + + _unusedButtonCodes = newArrayWithUnusedButtonCodesAsNSNumbers; + + [self _postStatusWithAction:kHIDRemoteDNStatusActionUpdate]; +} + +- (void)setDelegate:(NSObject <HIDRemoteDelegate> *)newDelegate +{ + _delegate = newDelegate; +} + +- (NSObject <HIDRemoteDelegate> *)delegate +{ + return (_delegate); +} + +#pragma mark -- PUBLIC: Expert APIs -- +- (void)setEnableSecureEventInputWorkaround:(BOOL)newEnableSecureEventInputWorkaround +{ + _secureEventInputWorkAround = newEnableSecureEventInputWorkaround; + + if ([self isStarted]) + { + if (_mode != kHIDRemoteModeShared) + { + // Changes go live immediately + if (_secureEventInputWorkAround && !_statusSecureEventInputWorkAroundEnabled) + { + EnableSecureEventInput(); + _statusSecureEventInputWorkAroundEnabled = YES; + } + + if (!_secureEventInputWorkAround && _statusSecureEventInputWorkAroundEnabled) + { + DisableSecureEventInput(); + _statusSecureEventInputWorkAroundEnabled = NO; + } + } + } +} + +- (BOOL)enableSecureEventInputWorkaround +{ + return (_secureEventInputWorkAround); +} + +- (void)setExclusiveLockLendingEnabled:(BOOL)newExclusiveLockLendingEnabled +{ + if (newExclusiveLockLendingEnabled != _exclusiveLockLending) + { + _exclusiveLockLending = newExclusiveLockLendingEnabled; + + if (_exclusiveLockLending) + { + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemoteStatus object:nil]; + } + else + { + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemoteStatus object:nil]; + + [_waitForReturnByPID release]; + _waitForReturnByPID = nil; + } + } +} + +- (BOOL)exclusiveLockLendingEnabled +{ + return (_exclusiveLockLending); +} + +#pragma mark -- PRIVATE: Application becomes active / inactive handling for kHIDRemoteModeExclusiveAuto -- +- (void)_appStatusChanged:(NSNotification *)notification +{ + if (notification) + { + if (_autoRecoveryTimer) + { + [_autoRecoveryTimer invalidate]; + [_autoRecoveryTimer release]; + _autoRecoveryTimer = nil; + } + + if ([[notification name] isEqual:NSApplicationDidBecomeActiveNotification]) + { + if (_autoRecover) + { + // Delay autorecover by 0.1 to avoid race conditions + if ((_autoRecoveryTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0.1] interval:0.1 target:self selector:@selector(_delayedAutoRecovery:) userInfo:nil repeats:NO]) != nil) + { + // Using CFRunLoopAddTimer instead of [[NSRunLoop currentRunLoop] addTimer:.. for consistency with run loop modes. + // The kCFRunLoopCommonModes counterpart NSRunLoopCommonModes is only available in 10.5 and later, whereas this code + // is designed to be also compatible with 10.4. CFRunLoopTimerRef is "toll-free-bridged" with NSTimer since 10.0. + CFRunLoopAddTimer(CFRunLoopGetCurrent(), (CFRunLoopTimerRef)_autoRecoveryTimer, kCFRunLoopCommonModes); + } + } + } + + if ([[notification name] isEqual:NSApplicationWillResignActiveNotification]) + { + if (_mode == kHIDRemoteModeExclusiveAuto) + { + [self stopRemoteControl]; + _autoRecover = YES; + } + } + + if ([[notification name] isEqual:NSApplicationWillTerminateNotification]) + { + if ([self isStarted]) + { + [self stopRemoteControl]; + } + } + } +} + +- (void)_delayedAutoRecovery:(NSTimer *)aTimer +{ + [_autoRecoveryTimer invalidate]; + [_autoRecoveryTimer release]; + _autoRecoveryTimer = nil; + + if (_autoRecover) + { + [self startRemoteControl:kHIDRemoteModeExclusiveAuto]; + _autoRecover = NO; + } +} + + +#pragma mark -- PRIVATE: Distributed notifiations handling -- +- (void)_postStatusWithAction:(NSString *)action +{ + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kHIDRemoteDNHIDRemoteStatus + object:[NSString stringWithFormat:@"%d",getpid()] + userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:1], kHIDRemoteDNStatusHIDRemoteVersionKey, + [NSNumber numberWithUnsignedInt:(unsigned int)getpid()], kHIDRemoteDNStatusPIDKey, + [NSNumber numberWithInt:(int)_mode], kHIDRemoteDNStatusModeKey, + [NSNumber numberWithUnsignedInt:(unsigned int)[self activeRemoteControlCount]], kHIDRemoteDNStatusRemoteControlCountKey, + ((_unusedButtonCodes!=nil) ? _unusedButtonCodes : [NSArray array]), kHIDRemoteDNStatusUnusedButtonCodesKey, + action, kHIDRemoteDNStatusActionKey, + [[NSBundle mainBundle] bundleIdentifier], (NSString *)kCFBundleIdentifierKey, + _returnToPID, kHIDRemoteDNStatusReturnToPIDKey, + nil] + deliverImmediately:YES + ]; +} + +- (void)_handleNotifications:(NSNotification *)notification +{ + NSString *notificationName; + + if ((notification!=nil) && ((notificationName = [notification name]) != nil)) + { + if ([notificationName isEqual:kHIDRemoteDNHIDRemotePing]) + { + [self _postStatusWithAction:kHIDRemoteDNStatusActionUpdate]; + } + + if ([notificationName isEqual:kHIDRemoteDNHIDRemoteRetry]) + { + if ([self isStarted]) + { + BOOL retry = YES; + + if (([self delegate] != nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:shouldRetryExclusiveLockWithInfo:)])) + { + retry = [[self delegate] hidRemote:self shouldRetryExclusiveLockWithInfo:[notification userInfo]]; + } + + if (retry) + { + HIDRemoteMode restartInMode = _mode; + + if (restartInMode != kHIDRemoteModeNone) + { + [self stopRemoteControl]; + + [_returnToPID release]; + [self startRemoteControl:restartInMode]; + + _returnToPID = [[[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey] retain]; + } + } + } + } + + if (_exclusiveLockLending) + { + if ([notificationName isEqual:kHIDRemoteDNHIDRemoteStatus]) + { + NSString *action; + + if ((action = [[notification userInfo] objectForKey:kHIDRemoteDNStatusActionKey]) != nil) + { + if (_mode==kHIDRemoteModeExclusive) + { + if ([action isEqual:kHIDRemoteDNStatusActionStart]) + { + NSNumber *originPID = [[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey]; + + if ([originPID intValue] != getpid()) + { + if (([self delegate] != nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:lendExclusiveLockToApplicationWithInfo:)])) + { + if ([[self delegate] hidRemote:self lendExclusiveLockToApplicationWithInfo:[notification userInfo]]) + { + [_waitForReturnByPID release]; + _waitForReturnByPID = [originPID retain]; + + if (_waitForReturnByPID != nil) + { + BOOL cachedStatusSecureEventInputWorkAroundEnabled; + + // Workaround for what seems to be a missing lock on the counter in the EnableSecureEventInput() / DisableSecureEventInput() API + cachedStatusSecureEventInputWorkAroundEnabled = _statusSecureEventInputWorkAroundEnabled; + _statusSecureEventInputWorkAroundEnabled = NO; + + [self stopRemoteControl]; + + _statusSecureEventInputWorkAroundEnabled = cachedStatusSecureEventInputWorkAroundEnabled; + + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kHIDRemoteDNHIDRemoteRetry + object:[NSString stringWithFormat:@"%d", [_waitForReturnByPID intValue]] + userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInt:(unsigned int)getpid()], kHIDRemoteDNStatusPIDKey, + [[NSBundle mainBundle] bundleIdentifier], (NSString *)kCFBundleIdentifierKey, + nil] + deliverImmediately:YES]; + } + } + } + } + } + } + + if ((_mode == kHIDRemoteModeNone) && _waitForReturnByPID) + { + if ([action isEqual:kHIDRemoteDNStatusActionStop]) + { + NSNumber *pidNumber, *returnToPIDNumber; + + if (((pidNumber = [[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey]) != nil) && + ((returnToPIDNumber = [[notification userInfo] objectForKey:kHIDRemoteDNStatusReturnToPIDKey]) != nil)) + { + if ([pidNumber isEqual:_waitForReturnByPID] && ([returnToPIDNumber intValue] == getpid())) + { + [_waitForReturnByPID release]; + _waitForReturnByPID = nil; + + if (([self delegate] != nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:exclusiveLockReleasedByApplicationWithInfo:)])) + { + [[self delegate] hidRemote:self exclusiveLockReleasedByApplicationWithInfo:[notification userInfo]]; + } + else + { + [self startRemoteControl:kHIDRemoteModeExclusive]; + } + } + } + } + } + } + } + } + } +} + +#pragma mark -- PRIVATE: Service setup and destruction -- +- (BOOL)_prematchService:(io_object_t)service +{ + BOOL serviceMatches = NO; + NSString *ioClass; + NSNumber *candelairHIDRemoteCompatibilityMask; + + if (service != 0) + { + // IOClass matching + if ((ioClass = (NSString *)IORegistryEntryCreateCFProperty((io_registry_entry_t)service, + CFSTR(kIOClassKey), + kCFAllocatorDefault, + 0)) != nil) + { + // Match on Apple's AppleIRController + if ([ioClass isEqual:@"AppleIRController"]) + { + CFTypeRef candelairHIDRemoteCompatibilityDevice; + + serviceMatches = YES; + + if ((candelairHIDRemoteCompatibilityDevice = IORegistryEntryCreateCFProperty((io_registry_entry_t)service, CFSTR("CandelairHIDRemoteCompatibilityDevice"), kCFAllocatorDefault, 0)) != NULL) + { + if (CFEqual(kCFBooleanTrue, candelairHIDRemoteCompatibilityDevice)) + { + serviceMatches = NO; + } + + CFRelease (candelairHIDRemoteCompatibilityDevice); + } + } + + // Match on the virtual IOSPIRIT IR Controller + if ([ioClass isEqual:@"IOSPIRITIRController"]) + { + serviceMatches = YES; + } + + CFRelease((CFTypeRef)ioClass); + } + + // Match on services that claim compatibility with the HID Remote class (Candelair or third-party) by having a property of CandelairHIDRemoteCompatibilityMask = 1 <Type: Number> + if ((candelairHIDRemoteCompatibilityMask = (NSNumber *)IORegistryEntryCreateCFProperty((io_registry_entry_t)service, CFSTR("CandelairHIDRemoteCompatibilityMask"), kCFAllocatorDefault, 0)) != nil) + { + if ([candelairHIDRemoteCompatibilityMask isKindOfClass:[NSNumber class]]) + { + if ([candelairHIDRemoteCompatibilityMask unsignedIntValue] & kHIDRemoteCompatibilityFlagsStandardHIDRemoteDevice) + { + serviceMatches = YES; + } + else + { + serviceMatches = NO; + } + } + + CFRelease((CFTypeRef)candelairHIDRemoteCompatibilityMask); + } + } + + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:inspectNewHardwareWithService:prematchResult:)])) + { + serviceMatches = [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self inspectNewHardwareWithService:service prematchResult:serviceMatches]; + } + + return (serviceMatches); +} + +- (BOOL)_setupService:(io_object_t)service +{ + kern_return_t kernResult; + IOReturn returnCode; + HRESULT hResult; + SInt32 score; + BOOL opened = NO, queueStarted = NO; + IOHIDDeviceInterface122 **hidDeviceInterface = NULL; + IOCFPlugInInterface **cfPluginInterface = NULL; + IOHIDQueueInterface **hidQueueInterface = NULL; + io_object_t serviceNotification = 0; + CFRunLoopSourceRef queueEventSource = NULL; + NSMutableDictionary *hidAttribsDict = nil; + CFArrayRef hidElements = NULL; + NSError *error = nil; + UInt32 errorCode = 0; + + if (![self _prematchService:service]) + { + return (NO); + } + + do + { + // Create a plugin interface .. + kernResult = IOCreatePlugInInterfaceForService( service, + kIOHIDDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &cfPluginInterface, + &score); + + if (kernResult != kIOReturnSuccess) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:kernResult userInfo:nil]; + errorCode = 1; + break; + } + + + // .. use it to get the HID interface .. + hResult = (*cfPluginInterface)->QueryInterface( cfPluginInterface, + CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122), + (LPVOID)&hidDeviceInterface); + + if ((hResult!=S_OK) || (hidDeviceInterface==NULL)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:hResult userInfo:nil]; + errorCode = 2; + break; + } + + + // .. then open it .. + switch (_mode) + { + case kHIDRemoteModeShared: + hResult = (*hidDeviceInterface)->open(hidDeviceInterface, kIOHIDOptionsTypeNone); + break; + + case kHIDRemoteModeExclusive: + case kHIDRemoteModeExclusiveAuto: + hResult = (*hidDeviceInterface)->open(hidDeviceInterface, kIOHIDOptionsTypeSeizeDevice); + break; + + default: + goto cleanUp; // Ugh! But there are no "double breaks" available in C AFAIK .. + break; + } + + if (hResult!=S_OK) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:hResult userInfo:nil]; + errorCode = 3; + break; + } + + opened = YES; + + // .. query the HID elements .. + returnCode = (*hidDeviceInterface)->copyMatchingElements(hidDeviceInterface, + NULL, + &hidElements); + if ((returnCode != kIOReturnSuccess) || (hidElements==NULL)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 4; + + break; + } + + // Setup an event queue for HID events! + hidQueueInterface = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); + if (hidQueueInterface == NULL) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil]; + errorCode = 5; + + break; + } + + returnCode = (*hidQueueInterface)->create(hidQueueInterface, 0, 32); + if ((returnCode != kIOReturnSuccess) || (hidElements==NULL)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 6; + + break; + } + + + // Setup of attributes stored for this HID device + hidAttribsDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + [NSValue valueWithPointer:(const void *)cfPluginInterface], kHIDRemoteCFPluginInterface, + [NSValue valueWithPointer:(const void *)hidDeviceInterface], kHIDRemoteHIDDeviceInterface, + [NSValue valueWithPointer:(const void *)hidQueueInterface], kHIDRemoteHIDQueueInterface, + nil]; + + { + UInt32 i, hidElementCnt = CFArrayGetCount(hidElements); + NSMutableDictionary *cookieButtonCodeLUT = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *cookieCount = [[NSMutableDictionary alloc] init]; + + if ((cookieButtonCodeLUT==nil) || (cookieCount==nil)) + { + [cookieButtonCodeLUT release]; + cookieButtonCodeLUT = nil; + + [cookieCount release]; + cookieCount = nil; + + error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil]; + errorCode = 7; + + break; + } + + // Analyze the HID elements and find matching elements + for (i=0;i<hidElementCnt;i++) + { + CFDictionaryRef hidDict; + NSNumber *usage, *usagePage, *cookie; + HIDRemoteButtonCode buttonCode = kHIDRemoteButtonCodeNone; + + hidDict = CFArrayGetValueAtIndex(hidElements, i); + + usage = (NSNumber *) CFDictionaryGetValue(hidDict, CFSTR(kIOHIDElementUsageKey)); + usagePage = (NSNumber *) CFDictionaryGetValue(hidDict, CFSTR(kIOHIDElementUsagePageKey)); + cookie = (NSNumber *) CFDictionaryGetValue(hidDict, CFSTR(kIOHIDElementCookieKey)); + + if (usage && usagePage && cookie) + { + // Find the button codes for the ID combos + switch ([usagePage unsignedIntValue]) + { + case kHIDPage_Consumer: + switch ([usage unsignedIntValue]) + { + case kHIDUsage_Csmr_Rewind: + buttonCode = kHIDRemoteButtonCodeLeftHold; + break; + + case kHIDUsage_Csmr_FastForward: + buttonCode = kHIDRemoteButtonCodeRightHold; + break; + + case kHIDUsage_Csmr_Menu: + buttonCode = kHIDRemoteButtonCodeMenuHold; + break; + } + break; + + case kHIDPage_GenericDesktop: + switch ([usage unsignedIntValue]) + { + case kHIDUsage_GD_SystemAppMenu: + buttonCode = kHIDRemoteButtonCodeMenu; + break; + + case kHIDUsage_GD_SystemMenu: + buttonCode = kHIDRemoteButtonCodePlayPause; + break; + + case kHIDUsage_GD_SystemMenuRight: + buttonCode = kHIDRemoteButtonCodeRight; + break; + + case kHIDUsage_GD_SystemMenuLeft: + buttonCode = kHIDRemoteButtonCodeLeft; + break; + + case kHIDUsage_GD_SystemMenuUp: + buttonCode = kHIDRemoteButtonCodePlus; + break; + + case kHIDUsage_GD_SystemMenuDown: + buttonCode = kHIDRemoteButtonCodeMinus; + break; + } + break; + + case 0x06: /* Resered */ + switch ([usage unsignedIntValue]) + { + case 0x22: + buttonCode = kHIDRemoteButtonCodeIDChanged; + break; + } + break; + + case 0xff01: /* Vendor specific */ + switch ([usage unsignedIntValue]) + { + case 0x23: + buttonCode = kHIDRemoteButtonCodePlayPauseHold; + break; + + #ifdef _HIDREMOTE_EXTENSIONS + #define _HIDREMOTE_EXTENSIONS_SECTION 2 + #include "HIDRemoteAdditions.h" + #undef _HIDREMOTE_EXTENSIONS_SECTION + #endif /* _HIDREMOTE_EXTENSIONS */ + } + break; + } + + // Did record match? + if (buttonCode != kHIDRemoteButtonCodeNone) + { + NSString *pairString = [[NSString alloc] initWithFormat:@"%u_%u", [usagePage unsignedIntValue], [usage unsignedIntValue]]; + NSNumber *buttonCodeNumber = [[NSNumber alloc] initWithUnsignedInt:(unsigned int)buttonCode]; + + [cookieCount setObject:buttonCodeNumber forKey:pairString]; + [cookieButtonCodeLUT setObject:buttonCodeNumber forKey:cookie]; + + (*hidQueueInterface)->addElement(hidQueueInterface, + (IOHIDElementCookie) [cookie unsignedIntValue], + 0); + + [buttonCodeNumber release]; + [pairString release]; + } + } + } + + // Compare number of *unique* matches (thus the cookieCount dictionary) with required minimum + if ([cookieCount count] < 10) + { + [cookieButtonCodeLUT release]; + cookieButtonCodeLUT = nil; + + [cookieCount release]; + cookieCount = nil; + + error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil]; + errorCode = 8; + + break; + } + + [hidAttribsDict setObject:cookieButtonCodeLUT forKey:kHIDRemoteCookieButtonCodeLUT]; + + [cookieButtonCodeLUT release]; + cookieButtonCodeLUT = nil; + + [cookieCount release]; + cookieCount = nil; + } + + // Finish setup of IOHIDQueueInterface with CFRunLoop + returnCode = (*hidQueueInterface)->createAsyncEventSource(hidQueueInterface, &queueEventSource); + if ((returnCode != kIOReturnSuccess) || (queueEventSource == NULL)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 9; + break; + } + + returnCode = (*hidQueueInterface)->setEventCallout(hidQueueInterface, HIDEventCallback, (void *)((intptr_t)service), (void *)self); + if (returnCode != kIOReturnSuccess) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 10; + break; + } + + CFRunLoopAddSource( CFRunLoopGetCurrent(), + queueEventSource, + kCFRunLoopCommonModes); + [hidAttribsDict setObject:[NSValue valueWithPointer:(const void *)queueEventSource] forKey:kHIDRemoteCFRunLoopSource]; + + returnCode = (*hidQueueInterface)->start(hidQueueInterface); + if (returnCode != kIOReturnSuccess) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 11; + break; + } + + queueStarted = YES; + + // Setup device notifications + returnCode = IOServiceAddInterestNotification( _notifyPort, + service, + kIOGeneralInterest, + ServiceNotificationCallback, + self, + &serviceNotification); + if ((returnCode != kIOReturnSuccess) || (serviceNotification==0)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 12; + break; + } + + [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:(unsigned int)serviceNotification] forKey:kHIDRemoteServiceNotification]; + + // Retain service + if (IOObjectRetain(service) != kIOReturnSuccess) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil]; + errorCode = 13; + break; + } + + [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:(unsigned int)service] forKey:kHIDRemoteService]; + + // Get some (somewhat optional) infos on the device + { + CFStringRef product, manufacturer, transport; + + if ((product = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service, + (CFStringRef) @"Product", + kCFAllocatorDefault, + 0)) != NULL) + { + if (CFGetTypeID(product) == CFStringGetTypeID()) + { + [hidAttribsDict setObject:(NSString *)product forKey:kHIDRemoteProduct]; + } + + CFRelease(product); + } + + if ((manufacturer = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service, + (CFStringRef) @"Manufacturer", + kCFAllocatorDefault, + 0)) != NULL) + { + if (CFGetTypeID(manufacturer) == CFStringGetTypeID()) + { + [hidAttribsDict setObject:(NSString *)manufacturer forKey:kHIDRemoteManufacturer]; + } + + CFRelease(manufacturer); + } + + if ((transport = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service, + (CFStringRef) @"Transport", + kCFAllocatorDefault, + 0)) != NULL) + { + if (CFGetTypeID(transport) == CFStringGetTypeID()) + { + [hidAttribsDict setObject:(NSString *)transport forKey:kHIDRemoteTransport]; + } + + CFRelease(transport); + } + } + + // Add it to the serviceAttribMap + [_serviceAttribMap setObject:hidAttribsDict forKey:[NSNumber numberWithUnsignedInt:(unsigned int)service]]; + + // And we're done with setup .. + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:foundNewHardwareWithAttributes:)])) + { + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self foundNewHardwareWithAttributes:hidAttribsDict]; + } + + [hidAttribsDict release]; + hidAttribsDict = nil; + + return(YES); + + }while(0); + + cleanUp: + + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:failedNewHardwareWithError:)])) + { + if (error) + { + error = [NSError errorWithDomain:[error domain] + code:[error code] + userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:errorCode] forKey:@"InternalErrorCode"] + ]; + } + + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self failedNewHardwareWithError:error]; + } + + // An error occured or this device is not of interest .. cleanup .. + if (serviceNotification) + { + IOObjectRelease(serviceNotification); + serviceNotification = 0; + } + + if (queueEventSource) + { + CFRunLoopSourceInvalidate(queueEventSource); + queueEventSource=NULL; + } + + if (hidQueueInterface) + { + if (queueStarted) + { + (*hidQueueInterface)->stop(hidQueueInterface); + } + (*hidQueueInterface)->dispose(hidQueueInterface); + (*hidQueueInterface)->Release(hidQueueInterface); + hidQueueInterface = NULL; + } + + if (hidAttribsDict) + { + [hidAttribsDict release]; + hidAttribsDict = nil; + } + + if (hidElements) + { + CFRelease(hidElements); + hidElements = NULL; + } + + if (hidDeviceInterface) + { + if (opened) + { + (*hidDeviceInterface)->close(hidDeviceInterface); + } + (*hidDeviceInterface)->Release(hidDeviceInterface); + // opened = NO; + hidDeviceInterface = NULL; + } + + if (cfPluginInterface) + { + IODestroyPlugInInterface(cfPluginInterface); + cfPluginInterface = NULL; + } + + return (NO); +} + +- (void)_destructService:(io_object_t)service +{ + NSNumber *serviceValue; + NSMutableDictionary *serviceDict = NULL; + + if ((serviceValue = [NSNumber numberWithUnsignedInt:(unsigned int)service]) == nil) + { + return; + } + + serviceDict = [_serviceAttribMap objectForKey:serviceValue]; + + if (serviceDict) + { + IOHIDDeviceInterface122 **hidDeviceInterface = NULL; + IOCFPlugInInterface **cfPluginInterface = NULL; + IOHIDQueueInterface **hidQueueInterface = NULL; + io_object_t serviceNotification = 0; + CFRunLoopSourceRef queueEventSource = NULL; + io_object_t theService = 0; + NSMutableDictionary *cookieButtonMap = nil; + NSTimer *simulateHoldTimer = nil; + + serviceNotification = (io_object_t) ([serviceDict objectForKey:kHIDRemoteServiceNotification] ? [[serviceDict objectForKey:kHIDRemoteServiceNotification] unsignedIntValue] : 0); + theService = (io_object_t) ([serviceDict objectForKey:kHIDRemoteService] ? [[serviceDict objectForKey:kHIDRemoteService] unsignedIntValue] : 0); + queueEventSource = (CFRunLoopSourceRef) ([serviceDict objectForKey:kHIDRemoteCFRunLoopSource] ? [[serviceDict objectForKey:kHIDRemoteCFRunLoopSource] pointerValue] : NULL); + hidQueueInterface = (IOHIDQueueInterface **) ([serviceDict objectForKey:kHIDRemoteHIDQueueInterface] ? [[serviceDict objectForKey:kHIDRemoteHIDQueueInterface] pointerValue] : NULL); + hidDeviceInterface = (IOHIDDeviceInterface122 **) ([serviceDict objectForKey:kHIDRemoteHIDDeviceInterface] ? [[serviceDict objectForKey:kHIDRemoteHIDDeviceInterface] pointerValue] : NULL); + cfPluginInterface = (IOCFPlugInInterface **) ([serviceDict objectForKey:kHIDRemoteCFPluginInterface] ? [[serviceDict objectForKey:kHIDRemoteCFPluginInterface] pointerValue] : NULL); + cookieButtonMap = (NSMutableDictionary *) [serviceDict objectForKey:kHIDRemoteCookieButtonCodeLUT]; + simulateHoldTimer = (NSTimer *) [serviceDict objectForKey:kHIDRemoteSimulateHoldEventsTimer]; + + [serviceDict retain]; + [_serviceAttribMap removeObjectForKey:serviceValue]; + + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:releasedHardwareWithAttributes:)])) + { + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self releasedHardwareWithAttributes:serviceDict]; + } + + if (simulateHoldTimer) + { + [simulateHoldTimer invalidate]; + } + + if (serviceNotification) + { + IOObjectRelease(serviceNotification); + } + + if (queueEventSource) + { + CFRunLoopRemoveSource( CFRunLoopGetCurrent(), + queueEventSource, + kCFRunLoopCommonModes); + } + + if (hidQueueInterface && cookieButtonMap) + { + NSEnumerator *cookieEnum = [cookieButtonMap keyEnumerator]; + NSNumber *cookie; + + while (cookie = [cookieEnum nextObject]) + { + if ((*hidQueueInterface)->hasElement(hidQueueInterface, (IOHIDElementCookie) [cookie unsignedIntValue])) + { + (*hidQueueInterface)->removeElement(hidQueueInterface, + (IOHIDElementCookie) [cookie unsignedIntValue]); + } + }; + } + + if (hidQueueInterface) + { + (*hidQueueInterface)->stop(hidQueueInterface); + (*hidQueueInterface)->dispose(hidQueueInterface); + (*hidQueueInterface)->Release(hidQueueInterface); + } + + if (hidDeviceInterface) + { + (*hidDeviceInterface)->close(hidDeviceInterface); + (*hidDeviceInterface)->Release(hidDeviceInterface); + } + + if (cfPluginInterface) + { + IODestroyPlugInInterface(cfPluginInterface); + } + + if (theService) + { + IOObjectRelease(theService); + } + + [serviceDict release]; + } +} + + +#pragma mark -- PRIVATE: HID Event handling -- +- (void)_simulateHoldEvent:(NSTimer *)aTimer +{ + NSMutableDictionary *hidAttribsDict; + NSTimer *shTimer; + NSNumber *shButtonCode; + + if ((hidAttribsDict = (NSMutableDictionary *)[aTimer userInfo]) != nil) + { + if (((shTimer = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsTimer]) != nil) && + ((shButtonCode = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsOriginButtonCode]) != nil)) + { + [shTimer invalidate]; + [hidAttribsDict removeObjectForKey:kHIDRemoteSimulateHoldEventsTimer]; + + [self _sendButtonCode:(((HIDRemoteButtonCode)[shButtonCode unsignedIntValue])|kHIDRemoteButtonCodeHoldMask) isPressed:YES]; + } + } +} + +- (void)_handleButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed hidAttribsDict:(NSMutableDictionary *)hidAttribsDict +{ + switch (buttonCode) + { + case kHIDRemoteButtonCodeIDChanged: + // Do nothing, this is handled seperately + break; + + case kHIDRemoteButtonCodePlus: + case kHIDRemoteButtonCodeMinus: + if (_simulateHoldEvents) + { + NSTimer *shTimer = nil; + NSNumber *shButtonCode = nil; + + [[hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsTimer] invalidate]; + + if (isPressed) + { + [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:buttonCode] forKey:kHIDRemoteSimulateHoldEventsOriginButtonCode]; + + if ((shTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0.7] interval:0.1 target:self selector:@selector(_simulateHoldEvent:) userInfo:hidAttribsDict repeats:NO]) != nil) + { + [hidAttribsDict setObject:shTimer forKey:kHIDRemoteSimulateHoldEventsTimer]; + + // Using CFRunLoopAddTimer instead of [[NSRunLoop currentRunLoop] addTimer:.. for consistency with run loop modes. + // The kCFRunLoopCommonModes counterpart NSRunLoopCommonModes is only available in 10.5 and later, whereas this code + // is designed to be also compatible with 10.4. CFRunLoopTimerRef is "toll-free-bridged" with NSTimer since 10.0. + CFRunLoopAddTimer(CFRunLoopGetCurrent(), (CFRunLoopTimerRef)shTimer, kCFRunLoopCommonModes); + + [shTimer release]; + + break; + } + } + else + { + shTimer = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsTimer]; + shButtonCode = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsOriginButtonCode]; + + if (shTimer && shButtonCode) + { + [self _sendButtonCode:(HIDRemoteButtonCode)[shButtonCode unsignedIntValue] isPressed:YES]; + [self _sendButtonCode:(HIDRemoteButtonCode)[shButtonCode unsignedIntValue] isPressed:NO]; + } + else + { + if (shButtonCode) + { + [self _sendButtonCode:(((HIDRemoteButtonCode)[shButtonCode unsignedIntValue])|kHIDRemoteButtonCodeHoldMask) isPressed:NO]; + } + } + } + + [hidAttribsDict removeObjectForKey:kHIDRemoteSimulateHoldEventsTimer]; + [hidAttribsDict removeObjectForKey:kHIDRemoteSimulateHoldEventsOriginButtonCode]; + + break; + } + + default: + [self _sendButtonCode:buttonCode isPressed:isPressed]; + break; + } +} + +- (void)_sendButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed +{ + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:eventWithButton:isPressed:)])) + { + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self eventWithButton:buttonCode isPressed:isPressed]; + } +} + +- (void)_hidEventFor:(io_service_t)hidDevice from:(IOHIDQueueInterface **)interface withResult:(IOReturn)result +{ + NSMutableDictionary *hidAttribsDict = [_serviceAttribMap objectForKey:[NSNumber numberWithUnsignedInt:(unsigned int)hidDevice]]; + + if (hidAttribsDict) + { + IOHIDQueueInterface **queueInterface = NULL; + + queueInterface = [[hidAttribsDict objectForKey:kHIDRemoteHIDQueueInterface] pointerValue]; + + if (interface == queueInterface) + { + NSNumber *lastButtonPressedNumber = nil; + HIDRemoteButtonCode lastButtonPressed = kHIDRemoteButtonCodeNone; + NSMutableDictionary *cookieButtonMap = nil; + + cookieButtonMap = [hidAttribsDict objectForKey:kHIDRemoteCookieButtonCodeLUT]; + + if ((lastButtonPressedNumber = [hidAttribsDict objectForKey:kHIDRemoteLastButtonPressed]) != nil) + { + lastButtonPressed = [lastButtonPressedNumber unsignedIntValue]; + } + + while (result == kIOReturnSuccess) + { + IOHIDEventStruct hidEvent; + AbsoluteTime supportedTime = { 0,0 }; + + result = (*queueInterface)->getNextEvent( queueInterface, + &hidEvent, + supportedTime, + 0); + + if (result == kIOReturnSuccess) + { + NSNumber *buttonCodeNumber = [cookieButtonMap objectForKey:[NSNumber numberWithUnsignedInt:(unsigned int) hidEvent.elementCookie]]; + + if (buttonCodeNumber) + { + HIDRemoteButtonCode buttonCode = [buttonCodeNumber unsignedIntValue]; + + if (hidEvent.value == 0) + { + if (buttonCode == lastButtonPressed) + { + [self _handleButtonCode:lastButtonPressed isPressed:NO hidAttribsDict:hidAttribsDict]; + lastButtonPressed = kHIDRemoteButtonCodeNone; + } + } + + if (hidEvent.value != 0) + { + if (lastButtonPressed != kHIDRemoteButtonCodeNone) + { + [self _handleButtonCode:lastButtonPressed isPressed:NO hidAttribsDict:hidAttribsDict]; + // lastButtonPressed = kHIDRemoteButtonCodeNone; + } + + if (buttonCode == kHIDRemoteButtonCodeIDChanged) + { + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:remoteIDChangedOldID:newID:)])) + { + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self remoteIDChangedOldID:_lastSeenRemoteID newID:hidEvent.value]; + } + + _lastSeenRemoteID = hidEvent.value; + } + + [self _handleButtonCode:buttonCode isPressed:YES hidAttribsDict:hidAttribsDict]; + lastButtonPressed = buttonCode; + } + } + } + }; + + [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:lastButtonPressed] forKey:kHIDRemoteLastButtonPressed]; + } + } +} + +#pragma mark -- PRIVATE: Notification handling -- +- (void)_serviceMatching:(io_iterator_t)iterator +{ + io_object_t matchingService = 0; + + while (matchingService = IOIteratorNext(iterator)) + { + [self _setupService:matchingService]; + + IOObjectRelease(matchingService); + }; +} + +- (void)_serviceNotificationFor:(io_service_t)service messageType:(natural_t)messageType messageArgument:(void *)messageArgument +{ + if (messageType == kIOMessageServiceIsTerminated) + { + [self _destructService:service]; + } +} + +@end + +#pragma mark -- PRIVATE: IOKitLib Callbacks -- + +static void HIDEventCallback( void * target, + IOReturn result, + void * refCon, + void * sender) +{ + HIDRemote *hidRemote = (HIDRemote *)refCon; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [hidRemote _hidEventFor:(io_service_t)((intptr_t)target) from:(IOHIDQueueInterface**)sender withResult:(IOReturn)result]; + + [pool release]; +} + + +static void ServiceMatchingCallback( void *refCon, + io_iterator_t iterator) +{ + HIDRemote *hidRemote = (HIDRemote *)refCon; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [hidRemote _serviceMatching:iterator]; + + [pool release]; +} + +static void ServiceNotificationCallback(void * refCon, + io_service_t service, + natural_t messageType, + void * messageArgument) +{ + HIDRemote *hidRemote = (HIDRemote *)refCon; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [hidRemote _serviceNotificationFor:service + messageType:messageType + messageArgument:messageArgument]; + + [pool release]; +} + +// Attribute dictionary keys +NSString *kHIDRemoteCFPluginInterface = @"CFPluginInterface"; +NSString *kHIDRemoteHIDDeviceInterface = @"HIDDeviceInterface"; +NSString *kHIDRemoteCookieButtonCodeLUT = @"CookieButtonCodeLUT"; +NSString *kHIDRemoteHIDQueueInterface = @"HIDQueueInterface"; +NSString *kHIDRemoteServiceNotification = @"ServiceNotification"; +NSString *kHIDRemoteCFRunLoopSource = @"CFRunLoopSource"; +NSString *kHIDRemoteLastButtonPressed = @"LastButtonPressed"; +NSString *kHIDRemoteService = @"Service"; +NSString *kHIDRemoteSimulateHoldEventsTimer = @"SimulateHoldEventsTimer"; +NSString *kHIDRemoteSimulateHoldEventsOriginButtonCode = @"SimulateHoldEventsOriginButtonCode"; + +NSString *kHIDRemoteManufacturer = @"Manufacturer"; +NSString *kHIDRemoteProduct = @"Product"; +NSString *kHIDRemoteTransport = @"Transport"; + +// Distributed notifications +NSString *kHIDRemoteDNHIDRemotePing = @"com.candelair.ping"; +NSString *kHIDRemoteDNHIDRemoteRetry = @"com.candelair.retry"; +NSString *kHIDRemoteDNHIDRemoteStatus = @"com.candelair.status"; + +// Distributed notifications userInfo keys and values +NSString *kHIDRemoteDNStatusHIDRemoteVersionKey = @"HIDRemoteVersion"; +NSString *kHIDRemoteDNStatusPIDKey = @"PID"; +NSString *kHIDRemoteDNStatusModeKey = @"Mode"; +NSString *kHIDRemoteDNStatusUnusedButtonCodesKey = @"UnusedButtonCodes"; +NSString *kHIDRemoteDNStatusActionKey = @"Action"; +NSString *kHIDRemoteDNStatusRemoteControlCountKey = @"RemoteControlCount"; +NSString *kHIDRemoteDNStatusReturnToPIDKey = @"ReturnToPID"; +NSString *kHIDRemoteDNStatusActionStart = @"start"; +NSString *kHIDRemoteDNStatusActionStop = @"stop"; +NSString *kHIDRemoteDNStatusActionUpdate = @"update"; |