5 // Created by Martin Kahr on 11.03.06.
6 // Copyright 2006 martinkahr.com. All rights reserved.
9 #import "AppleRemote.h"
11 const char* AppleRemoteDeviceName = "AppleIRController";
12 const int REMOTE_SWITCH_COOKIE=19;
14 @implementation AppleRemote
17 if ( self == [super init] ) {
18 openInExclusiveMode = YES;
20 hidDeviceInterface = NULL;
21 cookieToButtonMapping = [[NSMutableDictionary alloc] init];
23 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Plus] forKey:@"14_12_11_6_5_"];
24 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"14_13_11_6_5_"];
25 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"14_7_6_5_14_7_6_5_"];
26 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"14_8_6_5_14_8_6_5_"];
27 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"14_9_6_5_14_9_6_5_"];
28 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"14_10_6_5_14_10_6_5_"];
29 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_5_4_2_"];
30 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_5_3_2_"];
31 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_5_14_6_5_"];
32 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"18_14_6_5_18_14_6_5_"];
33 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"];
40 [self stopListening:self];
41 [cookieToButtonMapping release];
45 - (void) setRemoteId: (int) value {
52 - (BOOL) isRemoteAvailable {
53 io_object_t hidDevice = [self findAppleRemoteDevice];
55 IOObjectRelease(hidDevice);
62 - (BOOL) isListeningToRemote {
63 return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL);
66 - (void) setListeningToRemote: (BOOL) value {
68 [self stopListening:self];
70 [self startListening:self];
74 - (void) setDelegate: (id) _delegate {
75 if ([_delegate respondsToSelector:@selector(appleRemoteButton:pressedDown:)]==NO) return;
85 - (BOOL) isOpenInExclusiveMode {
86 return openInExclusiveMode;
88 - (void) setOpenInExclusiveMode: (BOOL) value {
89 openInExclusiveMode = value;
92 - (IBAction) startListening: (id) sender {
93 if ([self isListeningToRemote]) return;
95 io_object_t hidDevice = [self findAppleRemoteDevice];
96 if (hidDevice == 0) return;
98 if ([self createInterfaceForDevice:hidDevice] == NULL) {
102 if ([self initializeCookies]==NO) {
106 if ([self openDevice]==NO) {
112 [self stopListening:self];
115 IOObjectRelease(hidDevice);
118 - (IBAction) stopListening: (id) sender {
120 (*queue)->stop(queue);
123 (*queue)->dispose(queue);
125 //release the queue we allocated
126 (*queue)->Release(queue);
131 if (allCookies != nil) {
132 [allCookies autorelease];
136 if (hidDeviceInterface != NULL) {
138 (*hidDeviceInterface)->close(hidDeviceInterface);
140 //release the interface
141 (*hidDeviceInterface)->Release(hidDeviceInterface);
143 hidDeviceInterface = NULL;
149 @implementation AppleRemote (Singleton)
151 static AppleRemote* sharedInstance=nil;
153 + (AppleRemote*) sharedRemote {
154 @synchronized(self) {
155 if( sharedInstance == nil ) {
156 sharedInstance = [[self alloc] init];
159 return sharedInstance;
161 + (id)allocWithZone:(NSZone *)zone {
162 @synchronized(self) {
163 if (sharedInstance == nil) {
164 return [super allocWithZone:zone];
167 return sharedInstance;
169 - (id)copyWithZone:(NSZone *)zone {
175 - (unsigned)retainCount {
176 return UINT_MAX; //denotes an object that cannot be released
187 @implementation AppleRemote (PrivateMethods)
189 - (IOHIDQueueInterface**) queue {
193 - (IOHIDDeviceInterface**) hidDeviceInterface {
194 return hidDeviceInterface;
198 - (NSDictionary*) cookieToButtonMapping {
199 return cookieToButtonMapping;
202 - (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues {
203 NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString];
204 if (buttonId != nil) {
206 [delegate appleRemoteButton:[buttonId intValue] pressedDown: (sumOfValues>0)];
209 NSLog(@"Unknown button for cookiestring %@", cookieString);
215 /* Callback method for the device queue
216 Will be called for any event of any type (cookie) to which we subscribe
218 static void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) {
219 AppleRemote* remote = (AppleRemote*)target;
221 IOHIDEventStruct event;
222 AbsoluteTime zeroTime = {0,0};
223 NSMutableString* cookieString = [NSMutableString string];
224 SInt32 sumOfValues = 0;
225 while (result == kIOReturnSuccess)
227 result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0);
228 if ( result != kIOReturnSuccess )
231 if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie) {
232 [remote setRemoteId: event.value];
233 [remote handleEventWithCookieString: @"19_" sumOfValues: 0];
235 sumOfValues+=event.value;
236 [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]];
239 //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue);
242 [remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues];
246 @implementation AppleRemote (IOKitMethods)
248 - (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice {
250 IOCFPlugInInterface** plugInInterface = NULL;
251 HRESULT plugInResult = S_OK;
253 IOReturn ioReturnValue = kIOReturnSuccess;
255 hidDeviceInterface = NULL;
257 ioReturnValue = IOObjectGetClass(hidDevice, className);
259 if (ioReturnValue != kIOReturnSuccess) {
260 NSLog(@"Error: Failed to get class name.");
264 ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice,
265 kIOHIDDeviceUserClientTypeID,
266 kIOCFPlugInInterfaceID,
269 if (ioReturnValue == kIOReturnSuccess)
271 //Call a method of the intermediate plug-in to create the device interface
272 plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface);
274 if (plugInResult != S_OK) {
275 NSLog(@"Error: Couldn't create HID class device interface");
278 if (plugInInterface) (*plugInInterface)->Release(plugInInterface);
280 return hidDeviceInterface;
283 - (io_object_t) findAppleRemoteDevice {
284 CFMutableDictionaryRef hidMatchDictionary = NULL;
285 IOReturn ioReturnValue = kIOReturnSuccess;
286 io_iterator_t hidObjectIterator = 0;
287 io_object_t hidDevice = 0;
289 // Set up a matching dictionary to search the I/O Registry by class
290 // name for all HID class devices
291 hidMatchDictionary = IOServiceMatching(AppleRemoteDeviceName);
293 // Now search I/O Registry for matching devices.
294 ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator);
296 if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) {
297 hidDevice = IOIteratorNext(hidObjectIterator);
300 // release the iterator
301 IOObjectRelease(hidObjectIterator);
306 - (BOOL) initializeCookies {
307 IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
308 IOHIDElementCookie cookie;
312 NSArray* elements = nil;
313 NSDictionary* element;
316 if (!handle || !(*handle)) return NO;
318 // Copy all elements, since we're grabbing most of the elements
319 // for this device anyway, and thus, it's faster to iterate them
320 // ourselves. When grabbing only one or two elements, a matching
321 // dictionary should be passed in here instead of NULL.
322 success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements);
324 if (success == kIOReturnSuccess) {
326 [elements autorelease];
328 cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie));
329 memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS);
331 allCookies = [[NSMutableArray alloc] init];
333 for (i=0; i< [elements count]; i++) {
334 element = [elements objectAtIndex:i];
337 object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ];
338 if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
339 if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue;
340 cookie = (IOHIDElementCookie) [object longValue];
343 object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ];
344 if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
345 usage = [object longValue];
348 object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ];
349 if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
350 usagePage = [object longValue];
352 [allCookies addObject: [NSNumber numberWithInt:(int)cookie]];
361 - (BOOL) openDevice {
364 IOHIDOptionsType openMode = kIOHIDOptionsTypeNone;
365 if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice;
366 IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode);
368 if (ioReturnValue == KERN_SUCCESS) {
369 queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
371 result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost.
374 for(i=0; i<[allCookies count]; i++) {
375 IOHIDElementCookie cookie = (IOHIDElementCookie)[[allCookies objectAtIndex:i] intValue];
376 (*queue)->addElement(queue, cookie, 0);
379 // add callback for async events
380 CFRunLoopSourceRef eventSource;
381 ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource);
382 if (ioReturnValue == KERN_SUCCESS) {
383 ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL);
384 if (ioReturnValue == KERN_SUCCESS) {
385 CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
386 //start data delivery to queue
387 (*queue)->start(queue);
390 NSLog(@"Error when setting event callout");
393 NSLog(@"Error when creating async event source");
396 NSLog(@"Error when opening device");