1/***************************************************************************** 2 * AppleRemote.m 3 * AppleRemote 4 * $Id: 91cc731aad358e75883835eeb4e22aba9f91b6b0 $ 5 * 6 * Created by Martin Kahr on 11.03.06 under a MIT-style license. 7 * Copyright (c) 2006 martinkahr.com. All rights reserved. 8 * 9 * Permission is hereby granted, free of charge, to any person obtaining a 10 * copy of this software and associated documentation files (the "Software"), 11 * to deal in the Software without restriction, including without limitation 12 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 * and/or sell copies of the Software, and to permit persons to whom the 14 * Software is furnished to do so, subject to the following conditions: 15 * 16 * The above copyright notice and this permission notice shall be included 17 * in all copies or substantial portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 22 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 * THE SOFTWARE. 26 * 27 ***************************************************************************** 28 * 29 * Note that changes made by any members or contributors of the VideoLAN team 30 * (i.e. changes that were exclusively checked in to one of VideoLAN's source code 31 * repositories) are licensed under the GNU General Public License version 2, 32 * or (at your option) any later version. 33 * Thus, the following statements apply to our changes: 34 * 35 * Copyright (C) 2006-2009 VLC authors and VideoLAN 36 * Authors: Eric Petit <titer@m0k.org> 37 * Felix Kühne <fkuehne at videolan dot org> 38 * 39 * This program is free software; you can redistribute it and/or modify 40 * it under the terms of the GNU General Public License as published by 41 * the Free Software Foundation; either version 2 of the License, or 42 * (at your option) any later version. 43 * 44 * This program is distributed in the hope that it will be useful, 45 * but WITHOUT ANY WARRANTY; without even the implied warranty of 46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 47 * GNU General Public License for more details. 48 * 49 * You should have received a copy of the GNU General Public License 50 * along with this program; if not, write to the Free Software 51 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 52 *****************************************************************************/ 53 54#import "AppleRemote.h" 55 56/* this was added by the VideoLAN team to ensure Leopard-compatibility and is VLC-only */ 57#import "VLCMain.h" 58#import "CompatibilityFixes.h" 59 60const char* AppleRemoteDeviceName = "AppleIRController"; 61const int REMOTE_SWITCH_COOKIE=19; 62const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE=0.35; 63const NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL=0.4; 64 65@implementation AppleRemote 66 67#pragma public interface 68 69- (id)init 70{ 71 self = [super init]; 72 if (self) { 73 _openInExclusiveMode = YES; 74 queue = NULL; 75 hidDeviceInterface = NULL; 76 NSMutableDictionary * mutableCookieToButtonMapping = [[NSMutableDictionary alloc] init]; 77 78 if (OSX_CATALINA_AND_HIGHER) { 79 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Plus] forKey:@"35_23_22_17_14_4_3_35_23_22_4_3_"]; 80 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"35_23_22_18_14_4_3_35_23_22_4_3_"]; 81 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"35_24_23_22_4_3_35_24_23_22_4_3_"]; 82 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"35_25_23_22_4_3_35_25_23_22_4_3_"]; 83 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"35_26_23_22_4_3_35_26_23_22_4_3_"]; 84 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"35_27_23_22_4_3_35_27_23_22_4_3_"]; 85 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"35_23_22_16_14_4_3_"]; 86 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"35_23_22_15_14_4_3_"]; 87 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"35_23_22_4_3_35_23_22_4_3_"]; 88 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"39_35_23_22_4_3_39_35_23_22_4_3_"]; 89 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:k2009RemoteButtonPlay] forKey:@"35_23_22_10_4_3_35_23_22_10_4_3_"]; 90 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:k2009RemoteButtonFullscreen] forKey:@"35_23_22_4_3_35_23_22_4_3_"]; 91 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"44_35_23_22_4_3_35_23_22_4_3_"]; 92 } else { 93 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Plus] forKey:@"33_31_30_21_20_2_"]; 94 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"33_32_30_21_20_2_"]; 95 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"33_22_21_20_2_33_22_21_20_2_"]; 96 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"33_23_21_20_2_33_23_21_20_2_"]; 97 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"33_24_21_20_2_33_24_21_20_2_"]; 98 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"33_25_21_20_2_33_25_21_20_2_"]; 99 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"33_21_20_14_12_2_"]; 100 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"33_21_20_13_12_2_"]; 101 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"33_21_20_2_33_21_20_2_"]; 102 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"37_33_21_20_2_37_33_21_20_2_"]; 103 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:k2009RemoteButtonPlay] forKey:@"33_21_20_8_2_33_21_20_8_2_"]; 104 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:k2009RemoteButtonFullscreen] forKey:@"33_21_20_3_2_33_21_20_3_2_"]; 105 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"42_33_23_21_20_2_33_23_21_20_2_"]; 106 107 if (OSX_HIGH_SIERRA_AND_HIGHER) { 108 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Plus] forKey:@"33_21_20_15_12_2_"]; 109 [mutableCookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"33_21_20_16_12_2_"]; 110 } 111 } 112 113 _cookieToButtonMapping = [[NSDictionary alloc] initWithDictionary: mutableCookieToButtonMapping]; 114 115 /* defaults */ 116 _simulatesPlusMinusHold = YES; 117 _maximumClickCountTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE; 118 } 119 return self; 120} 121 122- (void) dealloc { 123 [self stopListening:self]; 124} 125 126- (int) remoteId { 127 return remoteId; 128} 129 130- (BOOL) remoteAvailable { 131 io_object_t hidDevice = [self findAppleRemoteDevice]; 132 if (hidDevice != 0) { 133 IOObjectRelease(hidDevice); 134 return YES; 135 } else { 136 return NO; 137 } 138} 139 140- (BOOL) listeningToRemote { 141 return (hidDeviceInterface != NULL && _allCookies != NULL && queue != NULL); 142} 143 144- (void) setListeningToRemote: (BOOL) value { 145 if (value == NO) { 146 [self stopListening:self]; 147 } else { 148 [self startListening:self]; 149 } 150} 151 152/* Delegates are not retained! 153 * http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html 154 * Delegating objects do not (and should not) retain their delegates. 155 * However, clients of delegating objects (applications, usually) are responsible for ensuring that their delegates are around 156 * to receive delegation messages. To do this, they may have to retain the delegate. */ 157- (void) setDelegate: (id) _delegate { 158 if (_delegate && [_delegate respondsToSelector:@selector(appleRemoteButton:pressedDown:clickCount:)]==NO) return; 159 160 delegate = _delegate; 161} 162- (id) delegate { 163 return delegate; 164} 165 166- (BOOL) clickCountingEnabled { 167 return self.clickCountEnabledButtons != 0; 168} 169- (void) setClickCountingEnabled: (BOOL) value { 170 if (value) { 171 [self setClickCountEnabledButtons: kRemoteButtonVolume_Plus | kRemoteButtonVolume_Minus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu | k2009RemoteButtonPlay | k2009RemoteButtonFullscreen]; 172 } else { 173 [self setClickCountEnabledButtons: 0]; 174 } 175} 176 177- (BOOL) listeningOnAppActivate { 178 id appDelegate = [NSApp delegate]; 179 return (appDelegate!=nil && [appDelegate isKindOfClass: [AppleRemoteApplicationDelegate class]]); 180} 181- (void) setListeningOnAppActivate: (BOOL) value { 182 if (value) { 183 if ([self listeningOnAppActivate]) return; 184 AppleRemoteApplicationDelegate* appDelegate = [[AppleRemoteApplicationDelegate alloc] initWithApplicationDelegate: [NSApp delegate]]; 185 /* NSApp does not retain its delegate therefore we keep retain count on 1 */ 186 [(NSApplication *)NSApp setDelegate: appDelegate]; 187 } else { 188 if ([self listeningOnAppActivate]==NO) return; 189 AppleRemoteApplicationDelegate* appDelegate = (AppleRemoteApplicationDelegate*)[NSApp delegate]; 190 id previousAppDelegate = [appDelegate applicationDelegate]; 191 [(NSApplication *)NSApp setDelegate: previousAppDelegate]; 192 } 193} 194 195- (IBAction) startListening: (id) sender { 196 if ([self listeningToRemote]) return; 197 198 io_object_t hidDevice = [self findAppleRemoteDevice]; 199 if (hidDevice == 0) return; 200 201 if ([self createInterfaceForDevice:hidDevice] == NULL) { 202 goto error; 203 } 204 205 if ([self initializeCookies]==NO) { 206 goto error; 207 } 208 209 if ([self openDevice]==NO) { 210 goto error; 211 } 212 goto cleanup; 213 214error: 215 [self stopListening:self]; 216 217cleanup: 218 IOObjectRelease(hidDevice); 219} 220 221- (IBAction) stopListening: (id) sender { 222 if (eventSource != NULL) { 223 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); 224 CFRelease(eventSource); 225 eventSource = NULL; 226 } 227 if (queue != NULL) { 228 (*queue)->stop(queue); 229 230 //dispose of queue 231 (*queue)->dispose(queue); 232 233 //release the queue we allocated 234 (*queue)->Release(queue); 235 236 queue = NULL; 237 } 238 239 if (_allCookies != nil) { 240 _allCookies = nil; 241 } 242 243 if (hidDeviceInterface != NULL) { 244 //close the device 245 (*hidDeviceInterface)->close(hidDeviceInterface); 246 247 //release the interface 248 (*hidDeviceInterface)->Release(hidDeviceInterface); 249 250 hidDeviceInterface = NULL; 251 } 252} 253 254@end 255 256@implementation AppleRemote (Singleton) 257 258static AppleRemote* sharedInstance=nil; 259 260+ (AppleRemote*) sharedRemote { 261 @synchronized(self) { 262 if (sharedInstance == nil) { 263 sharedInstance = [[self alloc] init]; 264 } 265 } 266 return sharedInstance; 267} 268+ (id)allocWithZone:(NSZone *)zone { 269 @synchronized(self) { 270 if (sharedInstance == nil) { 271 return [super allocWithZone:zone]; 272 } 273 } 274 return sharedInstance; 275} 276- (id)copyWithZone:(NSZone *)zone { 277 return self; 278} 279 280@end 281 282@implementation AppleRemote (PrivateMethods) 283 284- (void) setRemoteId: (int) value { 285 remoteId = value; 286} 287 288- (IOHIDQueueInterface**) queue { 289 return queue; 290} 291 292- (IOHIDDeviceInterface**) hidDeviceInterface { 293 return hidDeviceInterface; 294} 295 296- (NSDictionary*) cookieToButtonMapping { 297 return _cookieToButtonMapping; 298} 299 300- (NSString*) validCookieSubstring: (NSString*) cookieString { 301 if (cookieString == nil || [cookieString length] == 0) return nil; 302 NSEnumerator* keyEnum = [[self cookieToButtonMapping] keyEnumerator]; 303 NSString* key; 304 while((key = [keyEnum nextObject])) { 305 NSRange range = [cookieString rangeOfString:key]; 306 if (range.location == 0) return key; 307 } 308 return nil; 309} 310 311- (void) sendSimulatedPlusMinusEvent: (id) time { 312 BOOL startSimulateHold = NO; 313 AppleRemoteEventIdentifier event = lastPlusMinusEvent; 314 @synchronized(self) { 315 startSimulateHold = (lastPlusMinusEvent>0 && lastPlusMinusEventTime == [time doubleValue]); 316 } 317 if (startSimulateHold) { 318 lastEventSimulatedHold = YES; 319 event = (event==kRemoteButtonVolume_Plus) ? kRemoteButtonVolume_Plus_Hold : kRemoteButtonVolume_Minus_Hold; 320 [delegate appleRemoteButton:event pressedDown: YES clickCount: 1]; 321 } 322} 323 324- (void) sendRemoteButtonEvent: (AppleRemoteEventIdentifier) event pressedDown: (BOOL) pressedDown { 325 if (delegate) { 326 if (self.simulatesPlusMinusHold) { 327 if (event == kRemoteButtonVolume_Plus || event == kRemoteButtonVolume_Minus) { 328 if (pressedDown) { 329 lastPlusMinusEvent = event; 330 lastPlusMinusEventTime = [NSDate timeIntervalSinceReferenceDate]; 331 [self performSelector:@selector(sendSimulatedPlusMinusEvent:) 332 withObject:[NSNumber numberWithDouble:lastPlusMinusEventTime] 333 afterDelay:HOLD_RECOGNITION_TIME_INTERVAL]; 334 return; 335 } else { 336 if (lastEventSimulatedHold) { 337 event = (event==kRemoteButtonVolume_Plus) ? kRemoteButtonVolume_Plus_Hold : kRemoteButtonVolume_Minus_Hold; 338 lastPlusMinusEvent = 0; 339 lastEventSimulatedHold = NO; 340 } else { 341 @synchronized(self) { 342 lastPlusMinusEvent = 0; 343 } 344 pressedDown = YES; 345 } 346 } 347 } 348 } 349 350 if ((self.clickCountEnabledButtons & event) == event) { 351 if (pressedDown==NO && (event == kRemoteButtonVolume_Minus || event == kRemoteButtonVolume_Plus)) { 352 return; // this one is triggered automatically by the handler 353 } 354 NSNumber* eventNumber; 355 NSNumber* timeNumber; 356 @synchronized(self) { 357 lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate]; 358 if (lastClickCountEvent == event) { 359 eventClickCount = eventClickCount + 1; 360 } else { 361 eventClickCount = 1; 362 } 363 lastClickCountEvent = event; 364 timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; 365 eventNumber= [NSNumber numberWithUnsignedInt:event]; 366 } 367 [self performSelector: @selector(executeClickCountEvent:) 368 withObject: [NSArray arrayWithObjects: eventNumber, timeNumber, nil] 369 afterDelay: _maximumClickCountTimeDifference]; 370 } else { 371 [delegate appleRemoteButton:event pressedDown: pressedDown clickCount:1]; 372 } 373 } 374} 375 376- (void) executeClickCountEvent: (NSArray*) values { 377 AppleRemoteEventIdentifier event = [[values firstObject] unsignedIntValue]; 378 NSTimeInterval eventTimePoint = [[values objectAtIndex:1] doubleValue]; 379 380 BOOL finishedClicking = NO; 381 int finalClickCount = eventClickCount; 382 383 @synchronized(self) { 384 finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime); 385 if (finishedClicking) eventClickCount = 0; 386 } 387 388 if (finishedClicking) { 389 [delegate appleRemoteButton:event pressedDown: YES clickCount:finalClickCount]; 390 if ([self simulatesPlusMinusHold]==NO && (event == kRemoteButtonVolume_Minus || event == kRemoteButtonVolume_Plus)) { 391 // trigger a button release event, too 392 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]]; 393 [delegate appleRemoteButton:event pressedDown: NO clickCount:finalClickCount]; 394 } 395 } 396 397} 398 399- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues { 400 /* 401 if (previousRemainingCookieString) { 402 cookieString = [previousRemainingCookieString stringByAppendingString: cookieString]; 403 NSLog(@"New cookie string is %@", cookieString); 404 [previousRemainingCookieString release], previousRemainingCookieString=nil; 405 }*/ 406 if (cookieString == nil || [cookieString length] == 0) return; 407 NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString]; 408 if (buttonId != nil) { 409 [self sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)]; 410 } else { 411 // let's see if a number of events are stored in the cookie string. this does 412 // happen when the main thread is too busy to handle all incoming events in time. 413 NSString* subCookieString; 414 NSString* lastSubCookieString=nil; 415 while((subCookieString = [self validCookieSubstring: cookieString])) { 416 cookieString = [cookieString substringFromIndex: [subCookieString length]]; 417 lastSubCookieString = subCookieString; 418 if (self.processesBacklog) [self handleEventWithCookieString: subCookieString sumOfValues:sumOfValues]; 419 } 420 if (self.processesBacklog == NO && lastSubCookieString != nil) { 421 // process the last event of the backlog and assume that the button is not pressed down any longer. 422 // The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be 423 // a button pressed down event while in reality the user has released it. 424 // NSLog(@"processing last event of backlog"); 425 [self handleEventWithCookieString: lastSubCookieString sumOfValues:0]; 426 } 427 if ([cookieString length] > 0) { 428 msg_Warn( getIntf(), "Unknown AR button for cookiestring %s", [cookieString UTF8String]); 429 } 430 } 431} 432 433@end 434 435/* Callback method for the device queue 436Will be called for any event of any type (cookie) to which we subscribe 437*/ 438static void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) { 439 AppleRemote* remote = (__bridge AppleRemote*)target; 440 441 IOHIDEventStruct event; 442 AbsoluteTime zeroTime = {0,0}; 443 NSMutableString* cookieString = [NSMutableString string]; 444 SInt32 sumOfValues = 0; 445 while (result == kIOReturnSuccess) 446 { 447 result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0); 448 if ( result != kIOReturnSuccess ) 449 continue; 450 451 //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue); 452 453 if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie) { 454 [remote setRemoteId: event.value]; 455 [remote handleEventWithCookieString: @"19_" sumOfValues: 0]; 456 } else { 457 if (((int)event.elementCookie)!=5) { 458 sumOfValues+=event.value; 459 [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]]; 460 } 461 } 462 } 463 464 [remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues]; 465} 466 467@implementation AppleRemote (IOKitMethods) 468 469- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice { 470 io_name_t className; 471 IOCFPlugInInterface** plugInInterface = NULL; 472 HRESULT plugInResult = S_OK; 473 SInt32 score = 0; 474 IOReturn ioReturnValue = kIOReturnSuccess; 475 476 hidDeviceInterface = NULL; 477 478 ioReturnValue = IOObjectGetClass(hidDevice, className); 479 480 if (ioReturnValue != kIOReturnSuccess) { 481 msg_Err( getIntf(), "Failed to get IOKit class name."); 482 return NULL; 483 } 484 485 ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice, 486 kIOHIDDeviceUserClientTypeID, 487 kIOCFPlugInInterfaceID, 488 &plugInInterface, 489 &score); 490 if (ioReturnValue == kIOReturnSuccess) 491 { 492 //Call a method of the intermediate plug-in to create the device interface 493 plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface); 494 495 if (plugInResult != S_OK) { 496 msg_Err( getIntf(), "Couldn't create HID class device interface"); 497 } 498 // Release 499 if (plugInInterface) (*plugInInterface)->Release(plugInInterface); 500 } 501 return hidDeviceInterface; 502} 503 504- (io_object_t) findAppleRemoteDevice { 505 CFMutableDictionaryRef hidMatchDictionary = NULL; 506 IOReturn ioReturnValue = kIOReturnSuccess; 507 io_iterator_t hidObjectIterator = 0; 508 io_object_t hidDevice = 0; 509 510 // Set up a matching dictionary to search the I/O Registry by class 511 // name for all HID class devices 512 hidMatchDictionary = IOServiceMatching(AppleRemoteDeviceName); 513 514 // Now search I/O Registry for matching devices. 515 ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); 516 517 if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { 518 hidDevice = IOIteratorNext(hidObjectIterator); 519 } 520 521 // release the iterator 522 IOObjectRelease(hidObjectIterator); 523 524 return hidDevice; 525} 526 527- (BOOL) initializeCookies { 528 IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface; 529 IOHIDElementCookie cookie; 530 long usage; 531 long usagePage; 532 id object; 533 NSDictionary* element; 534 CFArrayRef elementsRef; 535 IOReturn success; 536 537 if (!handle || !(*handle)) return NO; 538 539 /* Copy all elements, since we're grabbing most of the elements 540 * for this device anyway, and thus, it's faster to iterate them 541 * ourselves. When grabbing only one or two elements, a matching 542 * dictionary should be passed in here instead of NULL. */ 543 success = (*handle)->copyMatchingElements(handle, NULL, &elementsRef); 544 545 if (success == kIOReturnSuccess) { 546 NSArray *elements = (__bridge NSArray *)elementsRef; 547 548 /* 549 cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie)); 550 memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS); 551 */ 552 NSMutableArray *mutableAllCookies = [[NSMutableArray alloc] init]; 553 NSUInteger elementCount = [elements count]; 554 for (NSUInteger i=0; i< elementCount; i++) { 555 element = [elements objectAtIndex:i]; 556 557 //Get cookie 558 object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ]; 559 if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; 560 if (object == 0 || CFGetTypeID((__bridge CFTypeRef)(object)) != CFNumberGetTypeID()) continue; 561 cookie = (IOHIDElementCookie) [object longValue]; 562 563 //Get usage 564 object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ]; 565 if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; 566 usage = [object longValue]; 567 568 //Get usage page 569 object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ]; 570 if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; 571 usagePage = [object longValue]; 572 573 [mutableAllCookies addObject: [NSNumber numberWithInt:(int)cookie]]; 574 } 575 _allCookies = [[NSArray alloc] initWithArray: mutableAllCookies]; 576 } else { 577 if (elementsRef) 578 CFRelease(elementsRef); 579 return NO; 580 } 581 582 if (elementsRef) 583 CFRelease(elementsRef); 584 return YES; 585} 586 587- (BOOL) openDevice { 588 HRESULT result; 589 590 IOHIDOptionsType openMode = kIOHIDOptionsTypeNone; 591 if ([self openInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice; 592 IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode); 593 594 if (ioReturnValue == KERN_SUCCESS) { 595 queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); 596 if (queue) { 597 result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost. 598 599 NSUInteger cookieCount = [_allCookies count]; 600 for(NSUInteger i=0; i<cookieCount; i++) { 601 IOHIDElementCookie cookie = (IOHIDElementCookie)[[_allCookies objectAtIndex:i] intValue]; 602 (*queue)->addElement(queue, cookie, 0); 603 } 604 605 // add callback for async events 606 ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource); 607 if (ioReturnValue == KERN_SUCCESS) { 608 ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, (__bridge void *)(self), NULL); 609 if (ioReturnValue == KERN_SUCCESS) { 610 CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); 611 //start data delivery to queue 612 (*queue)->start(queue); 613 return YES; 614 } else { 615 msg_Err( getIntf(), "Error when setting event callout"); 616 } 617 } else { 618 msg_Err( getIntf(), "Error when creating async event source"); 619 } 620 } else { 621 msg_Err( getIntf(), "Error when opening HUD device"); 622 } 623 } 624 return NO; 625} 626 627@end 628 629@implementation AppleRemoteApplicationDelegate 630 631- (id) initWithApplicationDelegate: (id) delegate { 632 if((self = [super init])) 633 applicationDelegate = delegate; 634 return self; 635} 636 637- (id) applicationDelegate { 638 return applicationDelegate; 639} 640 641- (void)applicationWillBecomeActive:(NSNotification *)aNotification { 642 if ([applicationDelegate respondsToSelector: @selector(applicationWillBecomeActive:)]) { 643 [applicationDelegate applicationWillBecomeActive: aNotification]; 644 } 645} 646- (void)applicationDidBecomeActive:(NSNotification *)aNotification { 647 [[AppleRemote sharedRemote] setListeningToRemote: YES]; 648 649 if ([applicationDelegate respondsToSelector: @selector(applicationDidBecomeActive:)]) { 650 [applicationDelegate applicationDidBecomeActive: aNotification]; 651 } 652} 653- (void)applicationWillResignActive:(NSNotification *)aNotification { 654 [[AppleRemote sharedRemote] setListeningToRemote: NO]; 655 656 if ([applicationDelegate respondsToSelector: @selector(applicationWillResignActive:)]) { 657 [applicationDelegate applicationWillResignActive: aNotification]; 658 } 659} 660- (void)applicationDidResignActive:(NSNotification *)aNotification { 661 if ([applicationDelegate respondsToSelector: @selector(applicationDidResignActive:)]) { 662 [applicationDelegate applicationDidResignActive: aNotification]; 663 } 664} 665 666- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { 667 NSMethodSignature* signature = [super methodSignatureForSelector: aSelector]; 668 if (signature == nil && applicationDelegate != nil) { 669 signature = [applicationDelegate methodSignatureForSelector: aSelector]; 670 } 671 return signature; 672} 673 674- (void)forwardInvocation:(NSInvocation *)invocation { 675 SEL aSelector = [invocation selector]; 676 677 if (applicationDelegate==nil || [applicationDelegate respondsToSelector:aSelector]==NO) { 678 [super forwardInvocation: invocation]; 679 return; 680 } 681 682 [invocation invokeWithTarget:applicationDelegate]; 683} 684@end 685