PageRenderTime 53ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/tools/EventClients/Clients/OSXRemote/HIDRemote/HIDRemote.m

https://gitlab.com/sloshedpuppie/LetsGoRetro
Objective C | 1599 lines | 1210 code | 270 blank | 119 comment | 259 complexity | 238c19d701279a0aed9a9e52ba2dd0c4 MD5 | raw file
  1. //
  2. // HIDRemote.m
  3. // HIDRemote V1.1.1 (14th December 2009)
  4. //
  5. // Created by Felix Schwarz on 06.04.07.
  6. // Copyright 2007-2009 IOSPIRIT GmbH. All rights reserved.
  7. //
  8. // The latest version of this class is available at
  9. // http://www.iospirit.com/developers/hidremote/
  10. //
  11. // ** LICENSE *************************************************************************
  12. //
  13. // Copyright (c) 2007-2009 IOSPIRIT GmbH (http://www.iospirit.com/)
  14. // All rights reserved.
  15. //
  16. // Redistribution and use in source and binary forms, with or without modification,
  17. // are permitted provided that the following conditions are met:
  18. //
  19. // * Redistributions of source code must retain the above copyright notice, this list
  20. // of conditions and the following disclaimer.
  21. //
  22. // * Redistributions in binary form must reproduce the above copyright notice, this
  23. // list of conditions and the following disclaimer in the documentation and/or other
  24. // materials provided with the distribution.
  25. //
  26. // * Neither the name of IOSPIRIT GmbH nor the names of its contributors may be used to
  27. // endorse or promote products derived from this software without specific prior
  28. // written permission.
  29. //
  30. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  31. // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
  33. // SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  34. // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  35. // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  36. // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  37. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  38. // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  39. // DAMAGE.
  40. //
  41. // ************************************************************************************
  42. // ************************************************************************************
  43. // ********************************** DOCUMENTATION ***********************************
  44. // ************************************************************************************
  45. //
  46. // - a reference is available at http://www.iospirit.com/developers/hidremote/reference/
  47. // - for a guide, please see http://www.iospirit.com/developers/hidremote/guide/
  48. //
  49. // ************************************************************************************
  50. #import "HIDRemote.h"
  51. // Callback Prototypes
  52. static void HIDEventCallback( void * target,
  53. IOReturn result,
  54. void * refcon,
  55. void * sender);
  56. static void ServiceMatchingCallback( void *refCon,
  57. io_iterator_t iterator);
  58. static void ServiceNotificationCallback(void * refCon,
  59. io_service_t service,
  60. natural_t messageType,
  61. void * messageArgument);
  62. static void SecureInputNotificationCallback( void * refCon,
  63. io_service_t service,
  64. natural_t messageType,
  65. void * messageArgument);
  66. // Shared HIDRemote instance
  67. static HIDRemote *sHIDRemote = nil;
  68. @implementation HIDRemote
  69. #pragma mark -- Init, dealloc & shared instance --
  70. + (HIDRemote *)sharedHIDRemote
  71. {
  72. if (!sHIDRemote)
  73. {
  74. sHIDRemote = [[HIDRemote alloc] init];
  75. }
  76. return (sHIDRemote);
  77. }
  78. - (id)init
  79. {
  80. if ((self = [super init]) != nil)
  81. {
  82. // Detect application becoming active/inactive
  83. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationDidBecomeActiveNotification object:[NSApplication sharedApplication]];
  84. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationWillResignActiveNotification object:[NSApplication sharedApplication]];
  85. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationWillTerminateNotification object:[NSApplication sharedApplication]];
  86. // Handle distributed notifications
  87. [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemotePing object:nil];
  88. [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemoteRetry object:[NSString stringWithFormat:@"%d", getpid()]];
  89. // Enabled by default: simulate hold events for plus/minus
  90. _simulateHoldEvents = YES;
  91. // Enabled by default: work around for a locking issue introduced with Security Update 2008-004 / 10.4.9 and beyond (credit for finding this workaround goes to Martin Kahr)
  92. _secureEventInputWorkAround = YES;
  93. _secureInputNotification = 0;
  94. // Initialize instance variables
  95. _lastSeenRemoteID = -1;
  96. _lastSeenModel = kHIDRemoteModelUndetermined;
  97. _unusedButtonCodes = [[NSMutableArray alloc] init];
  98. _exclusiveLockLending = NO;
  99. }
  100. return (self);
  101. }
  102. - (void)dealloc
  103. {
  104. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:[NSApplication sharedApplication]];
  105. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillResignActiveNotification object:[NSApplication sharedApplication]];
  106. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidBecomeActiveNotification object:[NSApplication sharedApplication]];
  107. [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemotePing object:nil];
  108. [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemoteRetry object:[NSString stringWithFormat:@"%d", getpid()]];
  109. [self stopRemoteControl];
  110. [self setExclusiveLockLendingEnabled:NO];
  111. [self setDelegate:nil];
  112. [_unusedButtonCodes release];
  113. _unusedButtonCodes = nil;
  114. [super dealloc];
  115. }
  116. #pragma mark -- PUBLIC: System Information --
  117. + (BOOL)isCandelairInstalled
  118. {
  119. mach_port_t masterPort = 0;
  120. kern_return_t kernResult;
  121. io_service_t matchingService = 0;
  122. BOOL isInstalled = NO;
  123. kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort);
  124. if (kernResult || !masterPort) { return(NO); }
  125. if ((matchingService = IOServiceGetMatchingService(masterPort, IOServiceMatching("IOSPIRITIRController"))) != 0)
  126. {
  127. isInstalled = YES;
  128. IOObjectRelease((io_object_t) matchingService);
  129. }
  130. mach_port_deallocate(mach_task_self(), masterPort);
  131. return (isInstalled);
  132. }
  133. + (BOOL)isCandelairInstallationRequiredForRemoteMode:(HIDRemoteMode)remoteMode
  134. {
  135. SInt32 systemVersion = 0;
  136. // Determine OS version
  137. if (Gestalt(gestaltSystemVersion, &systemVersion) == noErr)
  138. {
  139. switch (systemVersion)
  140. {
  141. case 0x1060: // OS 10.6
  142. case 0x1061: // OS 10.6.1
  143. // OS X 10.6(.0) and OS X 10.6.1 require the Candelair driver for to be installed,
  144. // so that third party apps can acquire an exclusive lock on the receiver HID Device
  145. // via IOKit.
  146. switch (remoteMode)
  147. {
  148. case kHIDRemoteModeExclusive:
  149. case kHIDRemoteModeExclusiveAuto:
  150. if (![self isCandelairInstalled])
  151. {
  152. return (YES);
  153. }
  154. break;
  155. }
  156. break;
  157. }
  158. }
  159. return (NO);
  160. }
  161. - (HIDRemoteAluminumRemoteSupportLevel)aluminiumRemoteSystemSupportLevel
  162. {
  163. HIDRemoteAluminumRemoteSupportLevel supportLevel = kHIDRemoteAluminumRemoteSupportLevelNone;
  164. NSEnumerator *attribDictsEnum;
  165. NSDictionary *hidAttribsDict;
  166. attribDictsEnum = [_serviceAttribMap objectEnumerator];
  167. while ((hidAttribsDict = [attribDictsEnum nextObject]) != nil)
  168. {
  169. NSNumber *deviceSupportLevel;
  170. if ((deviceSupportLevel = [hidAttribsDict objectForKey:kHIDRemoteAluminumRemoteSupportLevel]) != nil)
  171. {
  172. if ([deviceSupportLevel intValue] > supportLevel)
  173. {
  174. supportLevel = [deviceSupportLevel intValue];
  175. }
  176. }
  177. }
  178. return (supportLevel);
  179. }
  180. #pragma mark -- PUBLIC: Interface / API --
  181. - (BOOL)startRemoteControl:(HIDRemoteMode)hidRemoteMode
  182. {
  183. if ((_mode == kHIDRemoteModeNone) && (hidRemoteMode != kHIDRemoteModeNone))
  184. {
  185. kern_return_t kernReturn;
  186. CFMutableDictionaryRef matchDict=NULL;
  187. io_service_t rootService;
  188. do
  189. {
  190. // Get IOKit master port
  191. kernReturn = IOMasterPort(bootstrap_port, &_masterPort);
  192. if ((kernReturn!=kIOReturnSuccess) || (_masterPort==0)) { break; }
  193. // Setup notification port
  194. _notifyPort = IONotificationPortCreate(_masterPort);
  195. if ((_notifyRLSource = IONotificationPortGetRunLoopSource(_notifyPort)) != NULL)
  196. {
  197. CFRunLoopAddSource( CFRunLoopGetCurrent(),
  198. _notifyRLSource,
  199. kCFRunLoopCommonModes);
  200. }
  201. else
  202. {
  203. break;
  204. }
  205. // Setup SecureInput notification
  206. if ((hidRemoteMode == kHIDRemoteModeExclusive) || (hidRemoteMode == kHIDRemoteModeExclusiveAuto))
  207. {
  208. if ((rootService = IORegistryEntryFromPath(_masterPort, kIOServicePlane ":/")) != 0)
  209. {
  210. kernReturn = IOServiceAddInterestNotification( _notifyPort,
  211. rootService,
  212. kIOBusyInterest,
  213. SecureInputNotificationCallback,
  214. (void *)self,
  215. &_secureInputNotification);
  216. if (kernReturn != kIOReturnSuccess) { break; }
  217. [self _updateSessionInformation];
  218. }
  219. else
  220. {
  221. break;
  222. }
  223. }
  224. // Setup notification matching dict
  225. matchDict = IOServiceMatching(kIOHIDDeviceKey);
  226. CFRetain(matchDict);
  227. // Actually add notification
  228. kernReturn = IOServiceAddMatchingNotification( _notifyPort,
  229. kIOFirstMatchNotification,
  230. matchDict, // one reference count consumed by this call
  231. ServiceMatchingCallback,
  232. (void *) self,
  233. &_matchingServicesIterator);
  234. if (kernReturn != kIOReturnSuccess) { break; }
  235. // Setup serviceAttribMap
  236. _serviceAttribMap = [[NSMutableDictionary alloc] init];
  237. if (!_serviceAttribMap) { break; }
  238. // Phew .. everything went well!
  239. _mode = hidRemoteMode;
  240. CFRelease(matchDict);
  241. [self _serviceMatching:_matchingServicesIterator];
  242. [self _postStatusWithAction:kHIDRemoteDNStatusActionStart];
  243. return (YES);
  244. }while(0);
  245. // An error occured. Do necessary clean up.
  246. if (matchDict)
  247. {
  248. CFRelease(matchDict);
  249. matchDict = NULL;
  250. }
  251. [self stopRemoteControl];
  252. }
  253. return (NO);
  254. }
  255. - (void)stopRemoteControl
  256. {
  257. _autoRecover = NO;
  258. if (_autoRecoveryTimer)
  259. {
  260. [_autoRecoveryTimer invalidate];
  261. [_autoRecoveryTimer release];
  262. _autoRecoveryTimer = nil;
  263. }
  264. if (_serviceAttribMap)
  265. {
  266. NSDictionary *cloneDict = [[NSDictionary alloc] initWithDictionary:_serviceAttribMap];
  267. if (cloneDict)
  268. {
  269. NSEnumerator *mapKeyEnum = [cloneDict keyEnumerator];
  270. NSNumber *serviceValue;
  271. while ((serviceValue = [mapKeyEnum nextObject]) != nil)
  272. {
  273. [self _destructService:(io_object_t)[serviceValue unsignedIntValue]];
  274. };
  275. [cloneDict release];
  276. cloneDict = nil;
  277. }
  278. [_serviceAttribMap release];
  279. _serviceAttribMap = nil;
  280. }
  281. if (_matchingServicesIterator)
  282. {
  283. IOObjectRelease((io_object_t) _matchingServicesIterator);
  284. _matchingServicesIterator = 0;
  285. }
  286. if (_secureInputNotification)
  287. {
  288. IOObjectRelease((io_object_t) _secureInputNotification);
  289. _secureInputNotification = 0;
  290. }
  291. if (_notifyRLSource)
  292. {
  293. CFRunLoopSourceInvalidate(_notifyRLSource);
  294. _notifyRLSource = NULL;
  295. }
  296. if (_notifyPort)
  297. {
  298. IONotificationPortDestroy(_notifyPort);
  299. _notifyPort = NULL;
  300. }
  301. if (_masterPort)
  302. {
  303. mach_port_deallocate(mach_task_self(), _masterPort);
  304. }
  305. [self _postStatusWithAction:kHIDRemoteDNStatusActionStop];
  306. [_returnToPID release];
  307. _returnToPID = nil;
  308. _mode = kHIDRemoteModeNone;
  309. }
  310. - (BOOL)isStarted
  311. {
  312. return (_mode != kHIDRemoteModeNone);
  313. }
  314. - (unsigned)activeRemoteControlCount
  315. {
  316. return ([_serviceAttribMap count]);
  317. }
  318. - (SInt32)lastSeenRemoteControlID
  319. {
  320. return (_lastSeenRemoteID);
  321. }
  322. - (HIDRemoteModel)lastSeenModel
  323. {
  324. return (_lastSeenModel);
  325. }
  326. - (void)setLastSeenModel:(HIDRemoteModel)aModel
  327. {
  328. _lastSeenModel = aModel;
  329. }
  330. - (void)setSimulateHoldEvents:(BOOL)newSimulateHoldEvents
  331. {
  332. _simulateHoldEvents = newSimulateHoldEvents;
  333. }
  334. - (BOOL)simulateHoldEvents
  335. {
  336. return (_simulateHoldEvents);
  337. }
  338. - (NSArray *)unusedButtonCodes
  339. {
  340. return (_unusedButtonCodes);
  341. }
  342. - (void)setUnusedButtonCodes:(NSArray *)newArrayWithUnusedButtonCodesAsNSNumbers
  343. {
  344. [newArrayWithUnusedButtonCodesAsNSNumbers retain];
  345. [_unusedButtonCodes release];
  346. _unusedButtonCodes = newArrayWithUnusedButtonCodesAsNSNumbers;
  347. [self _postStatusWithAction:kHIDRemoteDNStatusActionUpdate];
  348. }
  349. - (void)setDelegate:(NSObject <HIDRemoteDelegate> *)newDelegate
  350. {
  351. _delegate = newDelegate;
  352. }
  353. - (NSObject <HIDRemoteDelegate> *)delegate
  354. {
  355. return (_delegate);
  356. }
  357. #pragma mark -- PUBLIC: Expert APIs --
  358. - (void)setEnableSecureEventInputWorkaround:(BOOL)newEnableSecureEventInputWorkaround
  359. {
  360. _secureEventInputWorkAround = newEnableSecureEventInputWorkaround;
  361. }
  362. - (BOOL)enableSecureEventInputWorkaround
  363. {
  364. return (_secureEventInputWorkAround);
  365. }
  366. - (void)setExclusiveLockLendingEnabled:(BOOL)newExclusiveLockLendingEnabled
  367. {
  368. if (newExclusiveLockLendingEnabled != _exclusiveLockLending)
  369. {
  370. _exclusiveLockLending = newExclusiveLockLendingEnabled;
  371. if (_exclusiveLockLending)
  372. {
  373. [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemoteStatus object:nil];
  374. }
  375. else
  376. {
  377. [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemoteStatus object:nil];
  378. [_waitForReturnByPID release];
  379. _waitForReturnByPID = nil;
  380. }
  381. }
  382. }
  383. - (BOOL)exclusiveLockLendingEnabled
  384. {
  385. return (_exclusiveLockLending);
  386. }
  387. #pragma mark -- PRIVATE: Application becomes active / inactive handling for kHIDRemoteModeExclusiveAuto --
  388. - (void)_appStatusChanged:(NSNotification *)notification
  389. {
  390. if (notification)
  391. {
  392. if (_autoRecoveryTimer)
  393. {
  394. [_autoRecoveryTimer invalidate];
  395. [_autoRecoveryTimer release];
  396. _autoRecoveryTimer = nil;
  397. }
  398. if ([[notification name] isEqual:NSApplicationDidBecomeActiveNotification])
  399. {
  400. if (_autoRecover)
  401. {
  402. // Delay autorecover by 0.1 to avoid race conditions
  403. if ((_autoRecoveryTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0.1] interval:0.1 target:self selector:@selector(_delayedAutoRecovery:) userInfo:nil repeats:NO]) != nil)
  404. {
  405. // Using CFRunLoopAddTimer instead of [[NSRunLoop currentRunLoop] addTimer:.. for consistency with run loop modes.
  406. // The kCFRunLoopCommonModes counterpart NSRunLoopCommonModes is only available in 10.5 and later, whereas this code
  407. // is designed to be also compatible with 10.4. CFRunLoopTimerRef is "toll-free-bridged" with NSTimer since 10.0.
  408. CFRunLoopAddTimer(CFRunLoopGetCurrent(), (CFRunLoopTimerRef)_autoRecoveryTimer, kCFRunLoopCommonModes);
  409. }
  410. }
  411. }
  412. if ([[notification name] isEqual:NSApplicationWillResignActiveNotification])
  413. {
  414. if (_mode == kHIDRemoteModeExclusiveAuto)
  415. {
  416. [self stopRemoteControl];
  417. _autoRecover = YES;
  418. }
  419. }
  420. if ([[notification name] isEqual:NSApplicationWillTerminateNotification])
  421. {
  422. if ([self isStarted])
  423. {
  424. [self stopRemoteControl];
  425. }
  426. }
  427. }
  428. }
  429. - (void)_delayedAutoRecovery:(NSTimer *)aTimer
  430. {
  431. [_autoRecoveryTimer invalidate];
  432. [_autoRecoveryTimer release];
  433. _autoRecoveryTimer = nil;
  434. if (_autoRecover)
  435. {
  436. [self startRemoteControl:kHIDRemoteModeExclusiveAuto];
  437. _autoRecover = NO;
  438. }
  439. }
  440. #pragma mark -- PRIVATE: Distributed notifiations handling --
  441. - (void)_postStatusWithAction:(NSString *)action
  442. {
  443. [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kHIDRemoteDNHIDRemoteStatus
  444. object:[NSString stringWithFormat:@"%d",getpid()]
  445. userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  446. [NSNumber numberWithInt:1], kHIDRemoteDNStatusHIDRemoteVersionKey,
  447. [NSNumber numberWithUnsignedInt:(unsigned int)getpid()], kHIDRemoteDNStatusPIDKey,
  448. [NSNumber numberWithInt:(int)_mode], kHIDRemoteDNStatusModeKey,
  449. [NSNumber numberWithUnsignedInt:(unsigned int)[self activeRemoteControlCount]], kHIDRemoteDNStatusRemoteControlCountKey,
  450. ((_unusedButtonCodes!=nil) ? _unusedButtonCodes : [NSArray array]), kHIDRemoteDNStatusUnusedButtonCodesKey,
  451. action, kHIDRemoteDNStatusActionKey,
  452. [[NSBundle mainBundle] bundleIdentifier], (NSString *)kCFBundleIdentifierKey,
  453. _returnToPID, kHIDRemoteDNStatusReturnToPIDKey,
  454. nil]
  455. deliverImmediately:YES
  456. ];
  457. }
  458. - (void)_handleNotifications:(NSNotification *)notification
  459. {
  460. NSString *notificationName;
  461. if ((notification!=nil) && ((notificationName = [notification name]) != nil))
  462. {
  463. if ([notificationName isEqual:kHIDRemoteDNHIDRemotePing])
  464. {
  465. [self _postStatusWithAction:kHIDRemoteDNStatusActionUpdate];
  466. }
  467. if ([notificationName isEqual:kHIDRemoteDNHIDRemoteRetry])
  468. {
  469. if ([self isStarted])
  470. {
  471. BOOL retry = YES;
  472. if (([self delegate] != nil) &&
  473. ([[self delegate] respondsToSelector:@selector(hidRemote:shouldRetryExclusiveLockWithInfo:)]))
  474. {
  475. retry = [[self delegate] hidRemote:self shouldRetryExclusiveLockWithInfo:[notification userInfo]];
  476. }
  477. if (retry)
  478. {
  479. HIDRemoteMode restartInMode = _mode;
  480. if (restartInMode != kHIDRemoteModeNone)
  481. {
  482. [self stopRemoteControl];
  483. [_returnToPID release];
  484. [self startRemoteControl:restartInMode];
  485. if (restartInMode != kHIDRemoteModeShared)
  486. {
  487. _returnToPID = [[[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey] retain];
  488. }
  489. }
  490. }
  491. else
  492. {
  493. NSNumber *cacheReturnPID = _returnToPID;
  494. _returnToPID = [[[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey] retain];
  495. [self _postStatusWithAction:kHIDRemoteDNStatusActionNoNeed];
  496. [_returnToPID release];
  497. _returnToPID = cacheReturnPID;
  498. }
  499. }
  500. }
  501. if (_exclusiveLockLending)
  502. {
  503. if ([notificationName isEqual:kHIDRemoteDNHIDRemoteStatus])
  504. {
  505. NSString *action;
  506. if ((action = [[notification userInfo] objectForKey:kHIDRemoteDNStatusActionKey]) != nil)
  507. {
  508. if ((_mode == kHIDRemoteModeNone) && _waitForReturnByPID)
  509. {
  510. NSNumber *pidNumber, *returnToPIDNumber;
  511. if ((pidNumber = [[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey]) != nil)
  512. {
  513. returnToPIDNumber = [[notification userInfo] objectForKey:kHIDRemoteDNStatusReturnToPIDKey];
  514. if ([action isEqual:kHIDRemoteDNStatusActionStart])
  515. {
  516. if ([pidNumber isEqual:_waitForReturnByPID])
  517. {
  518. NSNumber *startMode;
  519. if ((startMode = [[notification userInfo] objectForKey:kHIDRemoteDNStatusModeKey]) != nil)
  520. {
  521. if ([startMode intValue] == kHIDRemoteModeShared)
  522. {
  523. returnToPIDNumber = [NSNumber numberWithInt:getpid()];
  524. action = kHIDRemoteDNStatusActionNoNeed;
  525. }
  526. }
  527. }
  528. }
  529. if (returnToPIDNumber != nil)
  530. {
  531. if ([action isEqual:kHIDRemoteDNStatusActionStop] || [action isEqual:kHIDRemoteDNStatusActionNoNeed])
  532. {
  533. if ([pidNumber isEqual:_waitForReturnByPID] && ([returnToPIDNumber intValue] == getpid()))
  534. {
  535. [_waitForReturnByPID release];
  536. _waitForReturnByPID = nil;
  537. if (([self delegate] != nil) &&
  538. ([[self delegate] respondsToSelector:@selector(hidRemote:exclusiveLockReleasedByApplicationWithInfo:)]))
  539. {
  540. [[self delegate] hidRemote:self exclusiveLockReleasedByApplicationWithInfo:[notification userInfo]];
  541. }
  542. else
  543. {
  544. [self startRemoteControl:kHIDRemoteModeExclusive];
  545. }
  546. }
  547. }
  548. }
  549. }
  550. }
  551. if (_mode==kHIDRemoteModeExclusive)
  552. {
  553. if ([action isEqual:kHIDRemoteDNStatusActionStart])
  554. {
  555. NSNumber *originPID = [[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey];
  556. BOOL lendLock = YES;
  557. if ([originPID intValue] != getpid())
  558. {
  559. if (([self delegate] != nil) &&
  560. ([[self delegate] respondsToSelector:@selector(hidRemote:lendExclusiveLockToApplicationWithInfo:)]))
  561. {
  562. lendLock = [[self delegate] hidRemote:self lendExclusiveLockToApplicationWithInfo:[notification userInfo]];
  563. }
  564. if (lendLock)
  565. {
  566. [_waitForReturnByPID release];
  567. _waitForReturnByPID = [originPID retain];
  568. if (_waitForReturnByPID != nil)
  569. {
  570. [self stopRemoteControl];
  571. [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kHIDRemoteDNHIDRemoteRetry
  572. object:[NSString stringWithFormat:@"%d", [_waitForReturnByPID intValue]]
  573. userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  574. [NSNumber numberWithUnsignedInt:(unsigned int)getpid()], kHIDRemoteDNStatusPIDKey,
  575. [[NSBundle mainBundle] bundleIdentifier], (NSString *)kCFBundleIdentifierKey,
  576. nil]
  577. deliverImmediately:YES];
  578. }
  579. }
  580. }
  581. }
  582. }
  583. }
  584. }
  585. }
  586. }
  587. }
  588. #pragma mark -- PRIVATE: Service setup and destruction --
  589. - (BOOL)_prematchService:(io_object_t)service
  590. {
  591. BOOL serviceMatches = NO;
  592. NSString *ioClass;
  593. NSNumber *candelairHIDRemoteCompatibilityMask;
  594. if (service != 0)
  595. {
  596. // IOClass matching
  597. if ((ioClass = (NSString *)IORegistryEntryCreateCFProperty((io_registry_entry_t)service,
  598. CFSTR(kIOClassKey),
  599. kCFAllocatorDefault,
  600. 0)) != nil)
  601. {
  602. // Match on Apple's AppleIRController and old versions of the Remote Buddy IR Controller
  603. if ([ioClass isEqual:@"AppleIRController"] || [ioClass isEqual:@"RBIOKitAIREmu"])
  604. {
  605. CFTypeRef candelairHIDRemoteCompatibilityDevice;
  606. serviceMatches = YES;
  607. if ((candelairHIDRemoteCompatibilityDevice = IORegistryEntryCreateCFProperty((io_registry_entry_t)service, CFSTR("CandelairHIDRemoteCompatibilityDevice"), kCFAllocatorDefault, 0)) != NULL)
  608. {
  609. if (CFEqual(kCFBooleanTrue, candelairHIDRemoteCompatibilityDevice))
  610. {
  611. serviceMatches = NO;
  612. }
  613. CFRelease (candelairHIDRemoteCompatibilityDevice);
  614. }
  615. }
  616. // Match on the virtual IOSPIRIT IR Controller
  617. if ([ioClass isEqual:@"IOSPIRITIRController"])
  618. {
  619. serviceMatches = YES;
  620. }
  621. CFRelease((CFTypeRef)ioClass);
  622. }
  623. // Match on services that claim compatibility with the HID Remote class (Candelair or third-party) by having a property of CandelairHIDRemoteCompatibilityMask = 1 <Type: Number>
  624. if ((candelairHIDRemoteCompatibilityMask = (NSNumber *)IORegistryEntryCreateCFProperty((io_registry_entry_t)service, CFSTR("CandelairHIDRemoteCompatibilityMask"), kCFAllocatorDefault, 0)) != nil)
  625. {
  626. if ([candelairHIDRemoteCompatibilityMask isKindOfClass:[NSNumber class]])
  627. {
  628. if ([candelairHIDRemoteCompatibilityMask unsignedIntValue] & kHIDRemoteCompatibilityFlagsStandardHIDRemoteDevice)
  629. {
  630. serviceMatches = YES;
  631. }
  632. else
  633. {
  634. serviceMatches = NO;
  635. }
  636. }
  637. CFRelease((CFTypeRef)candelairHIDRemoteCompatibilityMask);
  638. }
  639. }
  640. if (([self delegate]!=nil) &&
  641. ([[self delegate] respondsToSelector:@selector(hidRemote:inspectNewHardwareWithService:prematchResult:)]))
  642. {
  643. serviceMatches = [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self inspectNewHardwareWithService:service prematchResult:serviceMatches];
  644. }
  645. return (serviceMatches);
  646. }
  647. - (HIDRemoteButtonCode)buttonCodeForUsage:(unsigned int)usage usagePage:(unsigned int)usagePage
  648. {
  649. HIDRemoteButtonCode buttonCode = kHIDRemoteButtonCodeNone;
  650. switch (usagePage)
  651. {
  652. case kHIDPage_Consumer:
  653. switch (usage)
  654. {
  655. case kHIDUsage_Csmr_MenuPick:
  656. // Aluminum Remote: Center
  657. buttonCode = (kHIDRemoteButtonCodeCenter|kHIDRemoteButtonCodeAluminumMask);
  658. break;
  659. case kHIDUsage_Csmr_ModeStep:
  660. // Aluminium Remote: Center Hold
  661. buttonCode = (kHIDRemoteButtonCodeCenterHold|kHIDRemoteButtonCodeAluminumMask);
  662. break;
  663. case kHIDUsage_Csmr_PlayOrPause:
  664. // Aluminum Remote: Play/Pause
  665. buttonCode = (kHIDRemoteButtonCodePlay|kHIDRemoteButtonCodeAluminumMask);
  666. break;
  667. case kHIDUsage_Csmr_Rewind:
  668. buttonCode = kHIDRemoteButtonCodeLeftHold;
  669. break;
  670. case kHIDUsage_Csmr_FastForward:
  671. buttonCode = kHIDRemoteButtonCodeRightHold;
  672. break;
  673. case kHIDUsage_Csmr_Menu:
  674. buttonCode = kHIDRemoteButtonCodeMenuHold;
  675. break;
  676. }
  677. break;
  678. case kHIDPage_GenericDesktop:
  679. switch (usage)
  680. {
  681. case kHIDUsage_GD_SystemAppMenu:
  682. buttonCode = kHIDRemoteButtonCodeMenu;
  683. break;
  684. case kHIDUsage_GD_SystemMenu:
  685. buttonCode = kHIDRemoteButtonCodeCenter;
  686. break;
  687. case kHIDUsage_GD_SystemMenuRight:
  688. buttonCode = kHIDRemoteButtonCodeRight;
  689. break;
  690. case kHIDUsage_GD_SystemMenuLeft:
  691. buttonCode = kHIDRemoteButtonCodeLeft;
  692. break;
  693. case kHIDUsage_GD_SystemMenuUp:
  694. buttonCode = kHIDRemoteButtonCodeUp;
  695. break;
  696. case kHIDUsage_GD_SystemMenuDown:
  697. buttonCode = kHIDRemoteButtonCodeDown;
  698. break;
  699. }
  700. break;
  701. case 0x06: /* Reserved */
  702. switch (usage)
  703. {
  704. case 0x22:
  705. buttonCode = kHIDRemoteButtonCodeIDChanged;
  706. break;
  707. }
  708. break;
  709. case 0xFF01: /* Vendor specific */
  710. switch (usage)
  711. {
  712. case 0x23:
  713. buttonCode = kHIDRemoteButtonCodeCenterHold;
  714. break;
  715. #ifdef _HIDREMOTE_EXTENSIONS
  716. #define _HIDREMOTE_EXTENSIONS_SECTION 2
  717. #include "HIDRemoteAdditions.h"
  718. #undef _HIDREMOTE_EXTENSIONS_SECTION
  719. #endif /* _HIDREMOTE_EXTENSIONS */
  720. }
  721. break;
  722. }
  723. return (buttonCode);
  724. }
  725. - (BOOL)_setupService:(io_object_t)service
  726. {
  727. kern_return_t kernResult;
  728. IOReturn returnCode;
  729. HRESULT hResult;
  730. SInt32 score;
  731. BOOL opened = NO, queueStarted = NO;
  732. IOHIDDeviceInterface122 **hidDeviceInterface = NULL;
  733. IOCFPlugInInterface **cfPluginInterface = NULL;
  734. IOHIDQueueInterface **hidQueueInterface = NULL;
  735. io_object_t serviceNotification = 0;
  736. CFRunLoopSourceRef queueEventSource = NULL;
  737. NSMutableDictionary *hidAttribsDict = nil;
  738. CFArrayRef hidElements = NULL;
  739. NSError *error = nil;
  740. UInt32 errorCode = 0;
  741. if (![self _prematchService:service])
  742. {
  743. return (NO);
  744. }
  745. do
  746. {
  747. // Create a plugin interface ..
  748. kernResult = IOCreatePlugInInterfaceForService( service,
  749. kIOHIDDeviceUserClientTypeID,
  750. kIOCFPlugInInterfaceID,
  751. &cfPluginInterface,
  752. &score);
  753. if (kernResult != kIOReturnSuccess)
  754. {
  755. error = [NSError errorWithDomain:NSMachErrorDomain code:kernResult userInfo:nil];
  756. errorCode = 1;
  757. break;
  758. }
  759. // .. use it to get the HID interface ..
  760. hResult = (*cfPluginInterface)->QueryInterface( cfPluginInterface,
  761. CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122),
  762. (LPVOID)&hidDeviceInterface);
  763. if ((hResult!=S_OK) || (hidDeviceInterface==NULL))
  764. {
  765. error = [NSError errorWithDomain:NSMachErrorDomain code:hResult userInfo:nil];
  766. errorCode = 2;
  767. break;
  768. }
  769. // .. then open it ..
  770. switch (_mode)
  771. {
  772. case kHIDRemoteModeShared:
  773. hResult = (*hidDeviceInterface)->open(hidDeviceInterface, kIOHIDOptionsTypeNone);
  774. break;
  775. case kHIDRemoteModeExclusive:
  776. case kHIDRemoteModeExclusiveAuto:
  777. hResult = (*hidDeviceInterface)->open(hidDeviceInterface, kIOHIDOptionsTypeSeizeDevice);
  778. break;
  779. default:
  780. goto cleanUp; // Ugh! But there are no "double breaks" available in C AFAIK ..
  781. break;
  782. }
  783. if (hResult!=S_OK)
  784. {
  785. error = [NSError errorWithDomain:NSMachErrorDomain code:hResult userInfo:nil];
  786. errorCode = 3;
  787. break;
  788. }
  789. opened = YES;
  790. // .. query the HID elements ..
  791. returnCode = (*hidDeviceInterface)->copyMatchingElements(hidDeviceInterface,
  792. NULL,
  793. &hidElements);
  794. if ((returnCode != kIOReturnSuccess) || (hidElements==NULL))
  795. {
  796. error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil];
  797. errorCode = 4;
  798. break;
  799. }
  800. // Setup an event queue for HID events!
  801. hidQueueInterface = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
  802. if (hidQueueInterface == NULL)
  803. {
  804. error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil];
  805. errorCode = 5;
  806. break;
  807. }
  808. returnCode = (*hidQueueInterface)->create(hidQueueInterface, 0, 32);
  809. if ((returnCode != kIOReturnSuccess) || (hidElements==NULL))
  810. {
  811. error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil];
  812. errorCode = 6;
  813. break;
  814. }
  815. // Setup of attributes stored for this HID device
  816. hidAttribsDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
  817. [NSValue valueWithPointer:(const void *)cfPluginInterface], kHIDRemoteCFPluginInterface,
  818. [NSValue valueWithPointer:(const void *)hidDeviceInterface], kHIDRemoteHIDDeviceInterface,
  819. [NSValue valueWithPointer:(const void *)hidQueueInterface], kHIDRemoteHIDQueueInterface,
  820. nil];
  821. {
  822. UInt32 i, hidElementCnt = CFArrayGetCount(hidElements);
  823. NSMutableDictionary *cookieButtonCodeLUT = [[NSMutableDictionary alloc] init];
  824. NSMutableDictionary *cookieCount = [[NSMutableDictionary alloc] init];
  825. if ((cookieButtonCodeLUT==nil) || (cookieCount==nil))
  826. {
  827. [cookieButtonCodeLUT release];
  828. cookieButtonCodeLUT = nil;
  829. [cookieCount release];
  830. cookieCount = nil;
  831. error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil];
  832. errorCode = 7;
  833. break;
  834. }
  835. // Analyze the HID elements and find matching elements
  836. for (i=0;i<hidElementCnt;i++)
  837. {
  838. CFDictionaryRef hidDict;
  839. NSNumber *usage, *usagePage, *cookie;
  840. HIDRemoteButtonCode buttonCode = kHIDRemoteButtonCodeNone;
  841. hidDict = CFArrayGetValueAtIndex(hidElements, i);
  842. usage = (NSNumber *) CFDictionaryGetValue(hidDict, CFSTR(kIOHIDElementUsageKey));
  843. usagePage = (NSNumber *) CFDictionaryGetValue(hidDict, CFSTR(kIOHIDElementUsagePageKey));
  844. cookie = (NSNumber *) CFDictionaryGetValue(hidDict, CFSTR(kIOHIDElementCookieKey));
  845. if (usage && usagePage && cookie)
  846. {
  847. // Find the button codes for the ID combos
  848. buttonCode = [self buttonCodeForUsage:[usage unsignedIntValue] usagePage:[usagePage unsignedIntValue]];
  849. #ifdef _HIDREMOTE_EXTENSIONS
  850. // Debug logging code
  851. #define _HIDREMOTE_EXTENSIONS_SECTION 3
  852. #include "HIDRemoteAdditions.h"
  853. #undef _HIDREMOTE_EXTENSIONS_SECTION
  854. #endif /* _HIDREMOTE_EXTENSIONS */
  855. // Did record match?
  856. if (buttonCode != kHIDRemoteButtonCodeNone)
  857. {
  858. NSString *pairString = [[NSString alloc] initWithFormat:@"%u_%u", [usagePage unsignedIntValue], [usage unsignedIntValue]];
  859. NSNumber *buttonCodeNumber = [[NSNumber alloc] initWithUnsignedInt:(unsigned int)buttonCode];
  860. #ifdef _HIDREMOTE_EXTENSIONS
  861. // Debug logging code
  862. #define _HIDREMOTE_EXTENSIONS_SECTION 4
  863. #include "HIDRemoteAdditions.h"
  864. #undef _HIDREMOTE_EXTENSIONS_SECTION
  865. #endif /* _HIDREMOTE_EXTENSIONS */
  866. [cookieCount setObject:buttonCodeNumber forKey:pairString];
  867. [cookieButtonCodeLUT setObject:buttonCodeNumber forKey:cookie];
  868. (*hidQueueInterface)->addElement(hidQueueInterface,
  869. (IOHIDElementCookie) [cookie unsignedIntValue],
  870. 0);
  871. [buttonCodeNumber release];
  872. [pairString release];
  873. }
  874. }
  875. }
  876. // Compare number of *unique* matches (thus the cookieCount dictionary) with required minimum
  877. if ([cookieCount count] < 10)
  878. {
  879. [cookieButtonCodeLUT release];
  880. cookieButtonCodeLUT = nil;
  881. [cookieCount release];
  882. cookieCount = nil;
  883. error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil];
  884. errorCode = 8;
  885. break;
  886. }
  887. [hidAttribsDict setObject:cookieButtonCodeLUT forKey:kHIDRemoteCookieButtonCodeLUT];
  888. [cookieButtonCodeLUT release];
  889. cookieButtonCodeLUT = nil;
  890. [cookieCount release];
  891. cookieCount = nil;
  892. }
  893. // Finish setup of IOHIDQueueInterface with CFRunLoop
  894. returnCode = (*hidQueueInterface)->createAsyncEventSource(hidQueueInterface, &queueEventSource);
  895. if ((returnCode != kIOReturnSuccess) || (queueEventSource == NULL))
  896. {
  897. error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil];
  898. errorCode = 9;
  899. break;
  900. }
  901. returnCode = (*hidQueueInterface)->setEventCallout(hidQueueInterface, HIDEventCallback, (void *)((intptr_t)service), (void *)self);
  902. if (returnCode != kIOReturnSuccess)
  903. {
  904. error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil];
  905. errorCode = 10;
  906. break;
  907. }
  908. CFRunLoopAddSource( CFRunLoopGetCurrent(),
  909. queueEventSource,
  910. kCFRunLoopCommonModes);
  911. [hidAttribsDict setObject:[NSValue valueWithPointer:(const void *)queueEventSource] forKey:kHIDRemoteCFRunLoopSource];
  912. returnCode = (*hidQueueInterface)->start(hidQueueInterface);
  913. if (returnCode != kIOReturnSuccess)
  914. {
  915. error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil];
  916. errorCode = 11;
  917. break;
  918. }
  919. queueStarted = YES;
  920. // Setup device notifications
  921. returnCode = IOServiceAddInterestNotification( _notifyPort,
  922. service,
  923. kIOGeneralInterest,
  924. ServiceNotificationCallback,
  925. self,
  926. &serviceNotification);
  927. if ((returnCode != kIOReturnSuccess) || (serviceNotification==0))
  928. {
  929. error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil];
  930. errorCode = 12;
  931. break;
  932. }
  933. [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:(unsigned int)serviceNotification] forKey:kHIDRemoteServiceNotification];
  934. // Retain service
  935. if (IOObjectRetain(service) != kIOReturnSuccess)
  936. {
  937. error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil];
  938. errorCode = 13;
  939. break;
  940. }
  941. [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:(unsigned int)service] forKey:kHIDRemoteService];
  942. // Get some (somewhat optional) infos on the device
  943. {
  944. CFStringRef product, manufacturer, transport;
  945. if ((product = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service,
  946. (CFStringRef) @"Product",
  947. kCFAllocatorDefault,
  948. 0)) != NULL)
  949. {
  950. if (CFGetTypeID(product) == CFStringGetTypeID())
  951. {
  952. [hidAttribsDict setObject:(NSString *)product forKey:kHIDRemoteProduct];
  953. }
  954. CFRelease(product);
  955. }
  956. if ((manufacturer = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service,
  957. (CFStringRef) @"Manufacturer",
  958. kCFAllocatorDefault,
  959. 0)) != NULL)
  960. {
  961. if (CFGetTypeID(manufacturer) == CFStringGetTypeID())
  962. {
  963. [hidAttribsDict setObject:(NSString *)manufacturer forKey:kHIDRemoteManufacturer];
  964. }
  965. CFRelease(manufacturer);
  966. }
  967. if ((transport = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service,
  968. (CFStringRef) @"Transport",
  969. kCFAllocatorDefault,
  970. 0)) != NULL)
  971. {
  972. if (CFGetTypeID(transport) == CFStringGetTypeID())
  973. {
  974. [hidAttribsDict setObject:(NSString *)transport forKey:kHIDRemoteTransport];
  975. }
  976. CFRelease(transport);
  977. }
  978. }
  979. // Determine Aluminum Remote support
  980. {
  981. CFNumberRef aluSupport;
  982. HIDRemoteAluminumRemoteSupportLevel supportLevel = kHIDRemoteAluminumRemoteSupportLevelNone;
  983. if ((_mode == kHIDRemoteModeExclusive) || (_mode == kHIDRemoteModeExclusiveAuto))
  984. {
  985. // Determine if this driver offers on-demand support for the Aluminum Remote (only relevant under OS versions < 10.6.2)
  986. if ((aluSupport = IORegistryEntryCreateCFProperty((io_registry_entry_t)service,
  987. (CFStringRef) @"AluminumRemoteSupportLevelOnDemand",
  988. kCFAllocatorDefault,
  989. 0)) != nil)
  990. {
  991. // There is => request the driver to enable it for us
  992. if (IORegistryEntrySetCFProperty((io_registry_entry_t)service,
  993. CFSTR("EnableAluminumRemoteSupportForMe"),
  994. [NSDictionary dictionaryWithObjectsAndKeys:
  995. [NSNumber numberWithLongLong:(long long)getpid()], @"pid",
  996. [NSNumber numberWithLongLong:(long long)getuid()], @"uid",
  997. nil]) == kIOReturnSuccess)
  998. {
  999. if (CFGetTypeID(aluSupport) == CFNumberGetTypeID())
  1000. {
  1001. supportLevel = (HIDRemoteAluminumRemoteSupportLevel) [(NSNumber *)aluSupport intValue];
  1002. }
  1003. [hidAttribsDict setObject:[NSNumber numberWithBool:YES] forKey:kHIDRemoteAluminumRemoteSupportOnDemand];
  1004. }
  1005. CFRelease(aluSupport);
  1006. }
  1007. }
  1008. if (supportLevel == kHIDRemoteAluminumRemoteSupportLevelNone)
  1009. {
  1010. if ((aluSupport = IORegistryEntryCreateCFProperty((io_registry_entry_t)service,
  1011. (CFStringRef) @"AluminumRemoteSupportLevel",
  1012. kCFAllocatorDefault,
  1013. 0)) != nil)
  1014. {
  1015. if (CFGetTypeID(aluSupport) == CFNumberGetTypeID())
  1016. {
  1017. supportLevel = (HIDRemoteAluminumRemoteSupportLevel) [(NSNumber *)aluSupport intValue];
  1018. }
  1019. CFRelease(aluSupport);
  1020. }
  1021. else
  1022. {
  1023. CFStringRef ioKitClassName;
  1024. if ((ioKitClassName = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service,
  1025. CFSTR(kIOClassKey),
  1026. kCFAllocatorDefault,
  1027. 0)) != nil)
  1028. {
  1029. if ([(NSString *)ioKitClassName isEqual:@"AppleIRController"])
  1030. {
  1031. SInt32 systemVersion;
  1032. if (Gestalt(gestaltSystemVersion, &systemVersion) == noErr)
  1033. {
  1034. if (systemVersion >= 0x1062)
  1035. {
  1036. // Support for the Aluminum Remote was added only with OS 10.6.2. Previous versions can not distinguish
  1037. // between the Center and the new, seperate Play/Pause button. They'll recognize both as presses of the
  1038. // "Center" button.
  1039. //
  1040. // You CAN, however, receive Aluminum Remote button presses even under OS 10.5 when using Remote Buddy's
  1041. // Virtual Remote. While Remote Buddy does support the Aluminum Remote across all OS releases it runs on,
  1042. // its Virtual Remote can only emulate Aluminum Remote button presses under OS 10.5 and up in order not to
  1043. // break compatibility with applications whose IR Remote code relies on driver internals. [13-Nov-09]
  1044. supportLevel = kHIDRemoteAluminumRemoteSupportLevelNative;
  1045. }
  1046. }
  1047. }
  1048. CFRelease(ioKitClassName);
  1049. }
  1050. }
  1051. }
  1052. [hidAttribsDict setObject:(NSNumber *)[NSNumber numberWithInt:(int)supportLevel] forKey:kHIDRemoteAluminumRemoteSupportLevel];
  1053. }
  1054. // Add it to the serviceAttribMap
  1055. [_serviceAttribMap setObject:hidAttribsDict forKey:[NSNumber numberWithUnsignedInt:(unsigned int)service]];
  1056. // And we're done with setup ..
  1057. if (([self delegate]!=nil) &&
  1058. ([[self delegate] respondsToSelector:@selector(hidRemote:foundNewHardwareWithAttributes:)]))
  1059. {
  1060. [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self foundNewHardwareWithAttributes:hidAttribsDict];
  1061. }
  1062. [hidAttribsDict release];
  1063. hidAttribsDict = nil;
  1064. return(YES);
  1065. }while(0);
  1066. cleanUp:
  1067. if (([self delegate]!=nil) &&
  1068. ([[self delegate] respondsToSelector:@selector(hidRemote:failedNewHardwareWithError:)]))
  1069. {
  1070. if (error)
  1071. {
  1072. error = [NSError errorWithDomain:[error domain]
  1073. code:[error code]
  1074. userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:errorCode] forKey:@"InternalErrorCode"]
  1075. ];
  1076. }
  1077. [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self failedNewHardwareWithError:error];
  1078. }
  1079. // An error occured or this device is not of interest .. cleanup ..
  1080. if (serviceNotification)
  1081. {
  1082. IOObjectRelease(serviceNotification);
  1083. serviceNotification = 0;
  1084. }
  1085. if (queueEventSource)
  1086. {
  1087. CFRunLoopSourceInvalidate(queueEventSource);
  1088. queueEventSource=NULL;
  1089. }
  1090. if (hidQueueInterface)
  1091. {
  1092. if (queueStarted)
  1093. {
  1094. (*hidQueueInterface)->stop(hidQueueInterface);
  1095. }
  1096. (*hidQueueInterface)->dispose(hidQueueInterface);
  1097. (*hidQueueInterface)->Release(hidQueueInterface);
  1098. hidQueueInterface = NULL;
  1099. }
  1100. if (hidAttribsDict)
  1101. {
  1102. [hidAttribsDict release];
  1103. hidAttribsDict = nil;
  1104. }
  1105. if (hidElements)
  1106. {
  1107. CFRelease(hidElements);
  1108. hidElements = NULL;
  1109. }
  1110. if (hidDeviceInterface)
  1111. {
  1112. if (opened)
  1113. {
  1114. (*hidDeviceInterface)->close(hidDeviceInterface);
  1115. }
  1116. (*hidDeviceInterface)->Release(hidDeviceInterface);
  1117. // opened = NO;
  1118. hidDeviceInterface = NULL;
  1119. }
  1120. if (cfPluginInterface)
  1121. {
  1122. IODestroyPlugInInterface(cfPluginInterface);
  1123. cfPluginInterface = NULL;
  1124. }
  1125. return (NO);
  1126. }
  1127. - (void)_destructService:(io_object_t)service
  1128. {
  1129. NSNumber *serviceValue;
  1130. NSMutableDictionary *serviceDict = NULL;
  1131. if ((serviceValue = [NSNumber numberWithUnsignedInt:(unsigned int)service]) == nil)
  1132. {
  1133. return;
  1134. }
  1135. serviceDict = [_serviceAttribMap objectForKey:serviceValue];
  1136. if (serviceDict)
  1137. {
  1138. IOHIDDeviceInterface122 **hidDeviceInterface = NULL;
  1139. IOCFPlugInInterface **cfPluginInterface = NULL;
  1140. IOHIDQueueInterface **hidQueueInterface = NULL;
  1141. io_object_t serviceNotification = 0;
  1142. CFRunLoopSourceRef queueEventSource = NULL;
  1143. io_object_t theService = 0;
  1144. NSMutableDictionary *cookieButtonMap = nil;
  1145. NSTimer *simulateHoldTimer = nil;
  1146. serviceNotification = (io_object_t) ([serviceDict objectForKey:kHIDRemoteServiceNotification] ? [[serviceDict objectForKey:kHIDRemoteServiceNotification] unsignedIntValue] : 0);
  1147. theService = (io_object_t) ([serviceDict objectForKey:kHIDRemoteService] ? [[serviceDict objectForKey:kHIDRemoteService] unsignedIntValue] : 0);
  1148. queueEventSource = (CFRunLoopSourceRef) ([serviceDict objectForKey:kHIDRemoteCFRunLoopSource] ? [[serviceDict objectForKey:kHIDRemoteCFRunLoopSource] pointerValue] : NULL);
  1149. hidQueueInterface = (IOHIDQueueInterface **) ([serviceDict objectForKey:kHIDRemoteHIDQueueInterface] ? [[serviceDict objectForKey:kHIDRemoteHIDQueueInterface] pointerValue] : NULL);
  1150. hidDeviceInterface = (IOHIDDeviceInterface122 **) ([serviceDict objectForKey:kHIDRemoteHIDDeviceInterface] ? [[serviceDict objectForKey:kHIDRemoteHIDDeviceInterface] pointerValue] : NULL);
  1151. cfPluginInterface = (IOCFPlugInInterface **) ([serviceDict objectForKey:kHIDRemoteCFPluginInterface] ? [[serviceDict objectForKey:kHIDRemoteCFPluginInterface] pointerValue] : NULL);
  1152. cookieButtonMap = (NSMutableDictionary *) [serviceDict objectForKey:kHIDRemoteCookieButtonCodeLUT];
  1153. simulateHoldTimer = (NSTimer *) [serviceDict objectForKey:kHIDRemoteSimulateHoldEventsTimer];
  1154. [serviceDict retain];
  1155. [_serviceAttribMap removeObjectForKey:serviceValue];
  1156. if (([serviceDict objectForKey:kHIDRemoteAluminumRemoteSupportOnDemand]!=nil) && [[serviceDict objectForKey:kHIDRemoteAluminumRemoteSupportOnDemand] boolValue] && (theService != 0))
  1157. {
  1158. // We previously requested the driver to enable Aluminum Remote support for us. Tell it to turn it off again - now that we no longer need it
  1159. IORegistryEntrySetCFProperty( (io_registry_entry_t)theService,
  1160. CFSTR("DisableAluminumRemoteSupportForMe"),
  1161. [NSDictionary dictionaryWithObjectsAndKeys:
  1162. [NSNumber numberWithLongLong:(long long)getpid()], @"pid",
  1163. [NSNumber numberWithLongLong:(long long)getuid()], @"uid",
  1164. nil]);
  1165. }
  1166. if (([self delegate]!=nil) &&
  1167. ([[self delegate] respondsToSelector:@selector(hidRemote:releasedHardwareWithAttributes:)]))
  1168. {
  1169. [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self releasedHardwareWithAttributes:serviceDict];
  1170. }
  1171. if (simulateHoldTimer)
  1172. {
  1173. [simulateHoldTimer invalidate];
  1174. }
  1175. if (serviceNotification)
  1176. {
  1177. IOObjectRelease(serviceNotification);
  1178. }
  1179. if (queueEventSource)
  1180. {
  1181. CFRunLoopRemoveSource( CFRunLoopGetCurrent(),
  1182. queueEventSource,
  1183. kCFRunLoopCommonModes);
  1184. }
  1185. if (hidQueueInterface && cookieButtonMap)
  1186. {
  1187. NSEnumerator *cookieEnum = [cookieButtonMap keyEnumerator];
  1188. NSNumber *cookie;
  1189. while ((cookie = [cookieEnum nextObject]) != nil)
  1190. {
  1191. if ((*hidQueueInterface)->hasElement(hidQueueInterface, (IOHIDElementCookie) [cookie unsignedIntValue]))
  1192. {
  1193. (*hidQueueInterface)->removeElement(hidQueueInterface,
  1194. (IOHIDElementCookie) [cookie unsignedIntValue]);
  1195. }
  1196. };
  1197. }
  1198. if (hidQueueInterface)
  1199. {
  1200. (*hidQueueInterface)->stop(hidQueueInterface);
  1201. (*hidQueueInterface)->dispose(hidQueueInterface);
  1202. (*hidQueueInterface)->Release(hidQueueInterface);
  1203. }
  1204. if (hidDeviceInterface)
  1205. {
  1206. (*hidDeviceInterface)->close(hidDeviceInterface);
  1207. (*hidDeviceInterface)->Release(hidDeviceInterface);
  1208. }
  1209. if (cfPluginInterface)
  1210. {
  1211. IODestroyPlugInInterface(cfPluginInterface);
  1212. }
  1213. if (theService)
  1214. {
  1215. IOObjectRelease(theService);
  1216. }
  1217. [serviceDict release];
  1218. }
  1219. }
  1220. #pragma mark -- PRIVATE: HID Event handling --
  1221. - (void)_simulateHoldEvent:(NSTimer *)aTimer
  1222. {
  1223. NSMutableDictionary *hidAttribsDict;
  1224. NSTimer *shTimer;
  1225. NSNumber *shButtonCode;
  1226. if ((hidAttribsDict = (NSMutableDictionary *)[aTimer userInfo]) != nil)
  1227. {
  1228. if (((shTimer = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsTimer]) != nil) &&
  1229. ((shButtonCode = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsOriginButtonCode]) != nil))
  1230. {
  1231. [shTimer invalidate];
  1232. [hidAttribsDict removeObjectForKey:kHIDRemoteSimulateHoldEventsTimer];
  1233. [self _sendButtonCode:(((HIDRemoteButtonCode)[shButtonCode unsignedIntValue])|kHIDRemoteButtonCodeHoldMask) isPressed:YES hidAttribsDict:hidAttribsDict];
  1234. }
  1235. }
  1236. }
  1237. - (void)_handleButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed hidAttribsDict:(NSMutableDictionary *)hidAttribsDict
  1238. {
  1239. switch (buttonCode)
  1240. {
  1241. case kHIDRemoteButtonCodeIDChanged:
  1242. // Do nothing, this is handled seperately
  1243. break;
  1244. case kHIDRemoteButtonCodeUp:
  1245. case kHIDRemoteButtonCodeDown:
  1246. if (_simulateHoldEvents)
  1247. {
  1248. NSTimer *shTimer = nil;
  1249. NSNumber *shButtonCode = nil;
  1250. [[hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsTimer] invalidate];
  1251. if (isPressed)
  1252. {
  1253. [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:buttonCode] forKey:kHIDRemoteSimulateHoldEventsOriginButtonCode];
  1254. if ((shTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0.7] interval:0.1 target:self selector:@selector(_simulateHoldEvent:) userInfo:hidAttribsDict repeats:NO]) != nil)
  1255. {
  1256. [hidAttribsDict setObject:shTimer forKey:kHIDRemoteSimulateHoldEventsTimer];
  1257. // Using CFRunLoopAddTimer instead of [[NSRunLoop currentRunLoop] addTimer:.. for consistency with run loop modes.
  1258. // The kCFRunLoopCommonModes counterpart NSRunLoopCommonModes is only available in 10.5 and later, whereas this code
  1259. // is designed to be also compatible with 10.4. CFRunLoopTimerRef is "toll-free-bridged" with NSTimer since 10.0.
  1260. CFRunLoopAddTimer(CFRunLoopGetCurrent(), (CFRunLoopTimerRef)shTimer, kCFRunLoopCommonModes);
  1261. [shTimer release];
  1262. break;
  1263. }
  1264. }
  1265. else
  1266. {
  1267. shTimer = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsTimer];
  1268. shButtonCode = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsOriginButtonCode];
  1269. if (shTimer && shButtonCode)
  1270. {
  1271. [self _sendButtonCode:(HIDRemoteButtonCode)[shButtonCode unsignedIntValue] isPressed:YES hidAttribsDict:hidAttribsDict];
  1272. [self _sendButtonCode:(HIDRemoteButtonCode)[shButtonCode unsignedIntValue] isPressed:NO hidAttribsDict:hidAttribsDict];
  1273. }
  1274. else
  1275. {
  1276. if (shButtonCode)
  1277. {
  1278. [self _sendButtonCode:(((HIDRemoteButtonCode)[shButtonCode unsignedIntValue])|kHIDRemoteButtonCodeHoldMask) isPressed:NO hidAttribsDict:hidAttribsDict];
  1279. }
  1280. }
  1281. }
  1282. [hidAttribsDict removeObjectForKey:kHIDRemoteSimulateHoldEventsTimer];
  1283. [hidAttribsDict removeObjectForKey:kHIDRemoteSimulateHoldEventsOriginButtonCode];
  1284. break;
  1285. }
  1286. default:
  1287. [self _sendButtonCode:buttonCode isPressed:isPressed hidAttribsDict:hidAttribsDict];
  1288. break;
  1289. }
  1290. }
  1291. - (void)_sendButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed hidAttribsDict:(NSMutableDictionary *)hidAttribsDict
  1292. {
  1293. if (([self delegate]!=nil) &&
  1294. ([[self delegate] respondsToSelector:@selector(hidRemote:eventWithButton:isPressed:fromHardwareWithAttributes:)]))
  1295. {
  1296. switch (buttonCode & (~kHIDRemoteButtonCodeAluminumMask))
  1297. {
  1298. case kHIDRemoteButtonCodePlay:
  1299. case kHIDRemoteButtonCodeCenter:
  1300. if (buttonCode & kHIDRemoteButtonCodeAluminumMask)
  1301. {
  1302. _lastSeenModel = kHIDRemoteModelAluminum;
  1303. _lastSeenModelRemoteID = _lastSeenRemoteID;
  1304. }
  1305. else
  1306. {
  1307. switch ((HIDRemoteAluminumRemoteSupportLevel)[[hidAttribsDict objectForKey:kHIDRemoteAluminumRemoteSupportLevel] intValue])
  1308. {
  1309. case kHIDRemoteAluminumRemoteSupportLevelNone:
  1310. case kHIDRemoteAluminumRemoteSupportLevelEmulation:
  1311. // Remote type can't be determined by just the Center button press
  1312. break;
  1313. case kHIDRemoteAluminumRemoteSupportLevelNative:
  1314. // Remote type can be safely determined by just the Center button press
  1315. if (((_lastSeenModel == kHIDRemoteModelAluminum) && (_lastSeenModelRemoteID != _lastSeenRemoteID)) ||
  1316. (_lastSeenModel == kHIDRemoteModelUndetermined))
  1317. {
  1318. _lastSeenModel = kHIDRemoteModelWhitePlastic;
  1319. }
  1320. break;
  1321. }
  1322. }
  1323. break;
  1324. }
  1325. // As soon as we have received a code that's unique to the Aluminum Remote, we can tell kHIDRemoteButtonCodePlayHold and kHIDRemoteButtonCodeCenterHold apart.
  1326. // Prior to that, a long press of the new "Play" button will be submitted as a "kHIDRemoteButtonCodeCenterHold", not a "kHIDRemoteButtonCodePlayHold" code.
  1327. if ((buttonCode == kHIDRemoteButtonCodeCenterHold) && (_lastSeenModel == kHIDRemoteModelAluminum))
  1328. {
  1329. but