aboutsummaryrefslogtreecommitdiff
path: root/tools/EventClients/Clients/OSXRemote/HIDRemote.m
diff options
context:
space:
mode:
Diffstat (limited to 'tools/EventClients/Clients/OSXRemote/HIDRemote.m')
-rw-r--r--tools/EventClients/Clients/OSXRemote/HIDRemote.m1544
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";