macosx remote: Fix a leak in the Apple Remote class.
[vlc.git] / modules / gui / macosx / AppleRemote.m
1 /*****************************************************************************
2  * AppleRemote.m
3  * AppleRemote
4  * $Id$
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 the VideoLAN team
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 "intf.h"
58
59 const char* AppleRemoteDeviceName = "AppleIRController";
60 const int REMOTE_SWITCH_COOKIE=19;
61 const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE=0.35;
62 const NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL=0.4;
63
64 @implementation AppleRemote
65
66 #pragma public interface
67
68 - (id) init {
69     if ( self = [super init] ) {
70         openInExclusiveMode = YES;
71         queue = NULL;
72         hidDeviceInterface = NULL;
73         cookieToButtonMapping = [[NSMutableDictionary alloc] init];
74
75         /* we're on Leopard and need to use a different set of cookies then we used to on Tiger and earlier */
76         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Plus]  forKey:@"31_29_28_18_"];
77         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"31_30_28_18_"];
78         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu]         forKey:@"31_20_18_31_20_18_"];
79         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay]         forKey:@"31_21_18_31_21_18_"];
80         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight]        forKey:@"31_22_18_31_22_18_"];
81         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft]         forKey:@"31_23_18_31_23_18_"];
82         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold]   forKey:@"31_18_4_2_"];
83         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold]    forKey:@"31_18_3_2_"];
84         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold]    forKey:@"31_18_31_18_"];
85         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep]   forKey:@"35_31_18_35_31_18_"];
86         [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched]   forKey:@"19_"];
87
88         /* defaults */
89         [self setSimulatesPlusMinusHold: YES];
90         maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE;
91     }
92
93     return self;
94 }
95
96 - (void) dealloc {
97     [self stopListening:self];
98     [cookieToButtonMapping release];
99     [super dealloc];
100 }
101
102 - (int) remoteId {
103     return remoteId;
104 }
105
106 - (BOOL) isRemoteAvailable {
107     io_object_t hidDevice = [self findAppleRemoteDevice];
108     if (hidDevice != 0) {
109         IOObjectRelease(hidDevice);
110         return YES;
111     } else {
112         return NO;
113     }
114 }
115
116 - (BOOL) isListeningToRemote {
117     return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL);
118 }
119
120 - (void) setListeningToRemote: (BOOL) value {
121     if (value == NO) {
122         [self stopListening:self];
123     } else {
124         [self startListening:self];
125     }
126 }
127
128 /* Delegates are not retained!
129  * http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html
130  * Delegating objects do not (and should not) retain their delegates.
131  * However, clients of delegating objects (applications, usually) are responsible for ensuring that their delegates are around
132  * to receive delegation messages. To do this, they may have to retain the delegate. */
133 - (void) setDelegate: (id) _delegate {
134     if (_delegate && [_delegate respondsToSelector:@selector(appleRemoteButton:pressedDown:clickCount:)]==NO) return;
135
136     delegate = _delegate;
137 }
138 - (id) delegate {
139     return delegate;
140 }
141
142 - (BOOL) isOpenInExclusiveMode {
143     return openInExclusiveMode;
144 }
145 - (void) setOpenInExclusiveMode: (BOOL) value {
146     openInExclusiveMode = value;
147 }
148
149 - (BOOL) clickCountingEnabled {
150     return clickCountEnabledButtons != 0;
151 }
152 - (void) setClickCountingEnabled: (BOOL) value {
153     if (value) {
154         [self setClickCountEnabledButtons: kRemoteButtonVolume_Plus | kRemoteButtonVolume_Minus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu];
155     } else {
156         [self setClickCountEnabledButtons: 0];
157     }
158 }
159
160 - (unsigned int) clickCountEnabledButtons {
161     return clickCountEnabledButtons;
162 }
163 - (void) setClickCountEnabledButtons: (unsigned int)value {
164     clickCountEnabledButtons = value;
165 }
166
167 - (NSTimeInterval) maximumClickCountTimeDifference {
168     return maxClickTimeDifference;
169 }
170 - (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff {
171     maxClickTimeDifference = timeDiff;
172 }
173
174 - (BOOL) processesBacklog {
175     return processesBacklog;
176 }
177 - (void) setProcessesBacklog: (BOOL) value {
178     processesBacklog = value;
179 }
180
181 - (BOOL) listeningOnAppActivate {
182     id appDelegate = [NSApp delegate];
183     return (appDelegate!=nil && [appDelegate isKindOfClass: [AppleRemoteApplicationDelegate class]]);
184 }
185 - (void) setListeningOnAppActivate: (BOOL) value {
186     if (value) {
187         if ([self listeningOnAppActivate]) return;
188         AppleRemoteApplicationDelegate* appDelegate = [[AppleRemoteApplicationDelegate alloc] initWithApplicationDelegate: [NSApp delegate]];
189         /* NSApp does not retain its delegate therefore we keep retain count on 1 */
190         [NSApp setDelegate: appDelegate];
191     } else {
192         if ([self listeningOnAppActivate]==NO) return;
193         AppleRemoteApplicationDelegate* appDelegate = (AppleRemoteApplicationDelegate*)[NSApp delegate];
194         id previousAppDelegate = [appDelegate applicationDelegate];
195         [NSApp setDelegate: previousAppDelegate];
196         [appDelegate release];
197     }
198 }
199
200 - (BOOL) simulatesPlusMinusHold {
201     return simulatePlusMinusHold;
202 }
203 - (void) setSimulatesPlusMinusHold: (BOOL) value {
204     simulatePlusMinusHold = value;
205 }
206
207 - (IBAction) startListening: (id) sender {
208     if ([self isListeningToRemote]) return;
209
210     io_object_t hidDevice = [self findAppleRemoteDevice];
211     if (hidDevice == 0) return;
212
213     if ([self createInterfaceForDevice:hidDevice] == NULL) {
214         goto error;
215     }
216
217     if ([self initializeCookies]==NO) {
218         goto error;
219     }
220
221     if ([self openDevice]==NO) {
222         goto error;
223     }
224     goto cleanup;
225
226 error:
227     [self stopListening:self];
228
229 cleanup:
230     IOObjectRelease(hidDevice);
231 }
232
233 - (IBAction) stopListening: (id) sender {
234     if (eventSource != NULL) {
235         CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
236         CFRelease(eventSource);
237         eventSource = NULL;
238     }
239     if (queue != NULL) {
240         (*queue)->stop(queue);
241
242         //dispose of queue
243         (*queue)->dispose(queue);
244
245         //release the queue we allocated
246         (*queue)->Release(queue);
247
248         queue = NULL;
249     }
250
251     if (allCookies != nil) {
252         [allCookies autorelease];
253         allCookies = nil;
254     }
255
256     if (hidDeviceInterface != NULL) {
257         //close the device
258         (*hidDeviceInterface)->close(hidDeviceInterface);
259
260         //release the interface
261         (*hidDeviceInterface)->Release(hidDeviceInterface);
262
263         hidDeviceInterface = NULL;
264     }
265 }
266
267 @end
268
269 @implementation AppleRemote (Singleton)
270
271 static AppleRemote* sharedInstance=nil;
272
273 + (AppleRemote*) sharedRemote {
274     @synchronized(self) {
275         if (sharedInstance == nil) {
276             sharedInstance = [[self alloc] init];
277         }
278     }
279     return sharedInstance;
280 }
281 + (id)allocWithZone:(NSZone *)zone {
282     @synchronized(self) {
283         if (sharedInstance == nil) {
284             return [super allocWithZone:zone];
285         }
286     }
287     return sharedInstance;
288 }
289 - (id)copyWithZone:(NSZone *)zone {
290     return self;
291 }
292 - (id)retain {
293     return self;
294 }
295 - (NSUInteger)retainCount {
296     return UINT_MAX;  //denotes an object that cannot be released
297 }
298 - (void)release {
299     //do nothing
300 }
301 - (id)autorelease {
302     return self;
303 }
304
305 @end
306
307 @implementation AppleRemote (PrivateMethods)
308
309 - (void) setRemoteId: (int) value {
310     remoteId = value;
311 }
312
313 - (IOHIDQueueInterface**) queue {
314     return queue;
315 }
316
317 - (IOHIDDeviceInterface**) hidDeviceInterface {
318     return hidDeviceInterface;
319 }
320
321
322 - (NSDictionary*) cookieToButtonMapping {
323     return cookieToButtonMapping;
324 }
325
326 - (NSString*) validCookieSubstring: (NSString*) cookieString {
327     if (cookieString == nil || [cookieString length] == 0) return nil;
328     NSEnumerator* keyEnum = [[self cookieToButtonMapping] keyEnumerator];
329     NSString* key;
330     while(key = [keyEnum nextObject]) {
331         NSRange range = [cookieString rangeOfString:key];
332         if (range.location == 0) return key;
333     }
334     return nil;
335 }
336
337 - (void) sendSimulatedPlusMinusEvent: (id) time {
338     BOOL startSimulateHold = NO;
339     AppleRemoteEventIdentifier event = lastPlusMinusEvent;
340     @synchronized(self) {
341         startSimulateHold = (lastPlusMinusEvent>0 && lastPlusMinusEventTime == [time doubleValue]);
342     }
343     if (startSimulateHold) {
344         lastEventSimulatedHold = YES;
345         event = (event==kRemoteButtonVolume_Plus) ? kRemoteButtonVolume_Plus_Hold : kRemoteButtonVolume_Minus_Hold;
346         [delegate appleRemoteButton:event pressedDown: YES clickCount: 1];
347     }
348 }
349
350 - (void) sendRemoteButtonEvent: (AppleRemoteEventIdentifier) event pressedDown: (BOOL) pressedDown {
351     if (delegate) {
352         if (simulatePlusMinusHold) {
353             if (event == kRemoteButtonVolume_Plus || event == kRemoteButtonVolume_Minus) {
354                 if (pressedDown) {
355                     lastPlusMinusEvent = event;
356                     lastPlusMinusEventTime = [NSDate timeIntervalSinceReferenceDate];
357                     [self performSelector:@selector(sendSimulatedPlusMinusEvent:)
358                                withObject:[NSNumber numberWithDouble:lastPlusMinusEventTime]
359                                afterDelay:HOLD_RECOGNITION_TIME_INTERVAL];
360                     return;
361                 } else {
362                     if (lastEventSimulatedHold) {
363                         event = (event==kRemoteButtonVolume_Plus) ? kRemoteButtonVolume_Plus_Hold : kRemoteButtonVolume_Minus_Hold;
364                         lastPlusMinusEvent = 0;
365                         lastEventSimulatedHold = NO;
366                     } else {
367                         @synchronized(self) {
368                             lastPlusMinusEvent = 0;
369                         }
370                         pressedDown = YES;
371                     }
372                 }
373             }
374         }
375
376         if (([self clickCountEnabledButtons] & event) == event) {
377             if (pressedDown==NO && (event == kRemoteButtonVolume_Minus || event == kRemoteButtonVolume_Plus)) {
378                 return; // this one is triggered automatically by the handler
379             }
380             NSNumber* eventNumber;
381             NSNumber* timeNumber;
382             @synchronized(self) {
383                 lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate];
384                 if (lastClickCountEvent == event) {
385                     eventClickCount = eventClickCount + 1;
386                 } else {
387                     eventClickCount = 1;
388                 }
389                 lastClickCountEvent = event;
390                 timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime];
391                 eventNumber= [NSNumber numberWithUnsignedInt:event];
392             }
393             [self performSelector: @selector(executeClickCountEvent:)
394                        withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil]
395                        afterDelay: maxClickTimeDifference];
396         } else {
397             [delegate appleRemoteButton:event pressedDown: pressedDown clickCount:1];
398         }
399     }
400 }
401
402 - (void) executeClickCountEvent: (NSArray*) values {
403     AppleRemoteEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue];
404     NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue];
405
406     BOOL finishedClicking = NO;
407     int finalClickCount = eventClickCount;
408
409     @synchronized(self) {
410         finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime);
411         if (finishedClicking) eventClickCount = 0;
412     }
413
414     if (finishedClicking) {
415         [delegate appleRemoteButton:event pressedDown: YES clickCount:finalClickCount];
416         if ([self simulatesPlusMinusHold]==NO && (event == kRemoteButtonVolume_Minus || event == kRemoteButtonVolume_Plus)) {
417             // trigger a button release event, too
418             [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]];
419             [delegate appleRemoteButton:event pressedDown: NO clickCount:finalClickCount];
420         }
421     }
422
423 }
424
425 - (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues {
426     /*
427     if (previousRemainingCookieString) {
428         cookieString = [previousRemainingCookieString stringByAppendingString: cookieString];
429         NSLog(@"New cookie string is %@", cookieString);
430         [previousRemainingCookieString release], previousRemainingCookieString=nil;
431     }*/
432     if (cookieString == nil || [cookieString length] == 0) return;
433     NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString];
434     if (buttonId != nil) {
435         [self sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)];
436     } else {
437         // let's see if a number of events are stored in the cookie string. this does
438         // happen when the main thread is too busy to handle all incoming events in time.
439         NSString* subCookieString;
440         NSString* lastSubCookieString=nil;
441         while(subCookieString = [self validCookieSubstring: cookieString]) {
442             cookieString = [cookieString substringFromIndex: [subCookieString length]];
443             lastSubCookieString = subCookieString;
444             if (processesBacklog) [self handleEventWithCookieString: subCookieString sumOfValues:sumOfValues];
445         }
446         if (processesBacklog == NO && lastSubCookieString != nil) {
447             // process the last event of the backlog and assume that the button is not pressed down any longer.
448             // The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be
449             // a button pressed down event while in reality the user has released it.
450             // NSLog(@"processing last event of backlog");
451             [self handleEventWithCookieString: lastSubCookieString sumOfValues:0];
452         }
453         if ([cookieString length] > 0) {
454             NSLog(@"Unknown button for cookiestring %@", cookieString);
455         }
456     }
457 }
458
459 @end
460
461 /*  Callback method for the device queue
462 Will be called for any event of any type (cookie) to which we subscribe
463 */
464 static void QueueCallbackFunction(void* target,  IOReturn result, void* refcon, void* sender) {
465     AppleRemote* remote = (AppleRemote*)target;
466
467     IOHIDEventStruct event;
468     AbsoluteTime     zeroTime = {0,0};
469     NSMutableString* cookieString = [NSMutableString string];
470     SInt32           sumOfValues = 0;
471     while (result == kIOReturnSuccess)
472     {
473         result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0);
474         if ( result != kIOReturnSuccess )
475             continue;
476
477         //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue);
478
479         if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie) {
480             [remote setRemoteId: event.value];
481             [remote handleEventWithCookieString: @"19_" sumOfValues: 0];
482         } else {
483             if (((int)event.elementCookie)!=5) {
484                 sumOfValues+=event.value;
485                 [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]];
486             }
487         }
488     }
489
490     [remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues];
491 }
492
493 @implementation AppleRemote (IOKitMethods)
494
495 - (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice {
496     io_name_t               className;
497     IOCFPlugInInterface**   plugInInterface = NULL;
498     HRESULT                 plugInResult = S_OK;
499     SInt32                  score = 0;
500     IOReturn                ioReturnValue = kIOReturnSuccess;
501
502     hidDeviceInterface = NULL;
503
504     ioReturnValue = IOObjectGetClass(hidDevice, className);
505
506     if (ioReturnValue != kIOReturnSuccess) {
507         NSLog(@"Error: Failed to get class name.");
508         return NULL;
509     }
510
511     ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice,
512                                                       kIOHIDDeviceUserClientTypeID,
513                                                       kIOCFPlugInInterfaceID,
514                                                       &plugInInterface,
515                                                       &score);
516     if (ioReturnValue == kIOReturnSuccess)
517     {
518         //Call a method of the intermediate plug-in to create the device interface
519         plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface);
520
521         if (plugInResult != S_OK) {
522             NSLog(@"Error: Couldn't create HID class device interface");
523         }
524         // Release
525         if (plugInInterface) (*plugInInterface)->Release(plugInInterface);
526     }
527     return hidDeviceInterface;
528 }
529
530 - (io_object_t) findAppleRemoteDevice {
531     CFMutableDictionaryRef hidMatchDictionary = NULL;
532     IOReturn ioReturnValue = kIOReturnSuccess;
533     io_iterator_t hidObjectIterator = 0;
534     io_object_t hidDevice = 0;
535
536     // Set up a matching dictionary to search the I/O Registry by class
537     // name for all HID class devices
538     hidMatchDictionary = IOServiceMatching(AppleRemoteDeviceName);
539
540     // Now search I/O Registry for matching devices.
541     ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator);
542
543     if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) {
544         hidDevice = IOIteratorNext(hidObjectIterator);
545     }
546
547     // release the iterator
548     IOObjectRelease(hidObjectIterator);
549
550     return hidDevice;
551 }
552
553 - (BOOL) initializeCookies {
554     IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
555     IOHIDElementCookie      cookie;
556     long                    usage;
557     long                    usagePage;
558     id                      object;
559     NSArray*                elements = nil;
560     NSDictionary*           element;
561     IOReturn success;
562
563     if (!handle || !(*handle)) return NO;
564
565     /* Copy all elements, since we're grabbing most of the elements
566      * for this device anyway, and thus, it's faster to iterate them
567      * ourselves. When grabbing only one or two elements, a matching
568      * dictionary should be passed in here instead of NULL. */
569     success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements);
570
571     if (success == kIOReturnSuccess) {
572
573         [elements autorelease];
574         /*
575         cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie));
576         memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS);
577         */
578         allCookies = [[NSMutableArray alloc] init];
579         int i;
580         for (i=0; i< [elements count]; i++) {
581             element = [elements objectAtIndex:i];
582
583             //Get cookie
584             object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ];
585             if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
586             if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue;
587             cookie = (IOHIDElementCookie) [object longValue];
588
589             //Get usage
590             object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ];
591             if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
592             usage = [object longValue];
593
594             //Get usage page
595             object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ];
596             if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
597             usagePage = [object longValue];
598
599             [allCookies addObject: [NSNumber numberWithInt:(int)cookie]];
600         }
601     } else {
602         return NO;
603     }
604
605     return YES;
606 }
607
608 - (BOOL) openDevice {
609     HRESULT  result;
610
611     IOHIDOptionsType openMode = kIOHIDOptionsTypeNone;
612     if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice;
613     IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode);
614
615     if (ioReturnValue == KERN_SUCCESS) {
616         queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
617         if (queue) {
618             result = (*queue)->create(queue, 0, 12);    //depth: maximum number of elements in queue before oldest elements in queue begin to be lost.
619
620             int i=0;
621             for(i=0; i<[allCookies count]; i++) {
622                 IOHIDElementCookie cookie = (IOHIDElementCookie)[[allCookies objectAtIndex:i] intValue];
623                 (*queue)->addElement(queue, cookie, 0);
624             }
625
626             // add callback for async events
627             ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource);
628             if (ioReturnValue == KERN_SUCCESS) {
629                 ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL);
630                 if (ioReturnValue == KERN_SUCCESS) {
631                     CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
632                     //start data delivery to queue
633                     (*queue)->start(queue);
634                     return YES;
635                 } else {
636                     NSLog(@"Error when setting event callout");
637                 }
638             } else {
639                 NSLog(@"Error when creating async event source");
640             }
641         } else {
642             NSLog(@"Error when opening device");
643         }
644     }
645     return NO;
646 }
647
648 @end
649
650 @implementation AppleRemoteApplicationDelegate
651
652 - (id) initWithApplicationDelegate: (id) delegate {
653     if (self = [super init]) {
654         applicationDelegate = [delegate retain];
655     }
656     return self;
657 }
658
659 - (void) dealloc {
660     NSLog(@"Dealloc");
661     [applicationDelegate release];
662     [super dealloc];
663 }
664
665 - (id) applicationDelegate {
666     return applicationDelegate;
667 }
668
669 - (void)applicationWillBecomeActive:(NSNotification *)aNotification {
670     if ([applicationDelegate respondsToSelector: @selector(applicationWillBecomeActive:)]) {
671         [applicationDelegate applicationWillBecomeActive: aNotification];
672     }
673 }
674 - (void)applicationDidBecomeActive:(NSNotification *)aNotification {
675     [[AppleRemote sharedRemote] setListeningToRemote: YES];
676
677     if ([applicationDelegate respondsToSelector: @selector(applicationDidBecomeActive:)]) {
678         [applicationDelegate applicationDidBecomeActive: aNotification];
679     }
680 }
681 - (void)applicationWillResignActive:(NSNotification *)aNotification {
682     [[AppleRemote sharedRemote] setListeningToRemote: NO];
683
684     if ([applicationDelegate respondsToSelector: @selector(applicationWillResignActive:)]) {
685         [applicationDelegate applicationWillResignActive: aNotification];
686     }
687 }
688 - (void)applicationDidResignActive:(NSNotification *)aNotification {
689     if ([applicationDelegate respondsToSelector: @selector(applicationDidResignActive:)]) {
690         [applicationDelegate applicationDidResignActive: aNotification];
691     }
692 }
693
694 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
695     NSMethodSignature* signature = [super methodSignatureForSelector: aSelector];
696     if (signature == nil && applicationDelegate != nil) {
697         signature = [applicationDelegate methodSignatureForSelector: aSelector];
698     }
699     return signature;
700 }
701
702 - (void)forwardInvocation:(NSInvocation *)invocation {
703     SEL aSelector = [invocation selector];
704
705     if (applicationDelegate==nil || [applicationDelegate respondsToSelector:aSelector]==NO) {
706         [super forwardInvocation: invocation];
707         return;
708     }
709
710     [invocation invokeWithTarget:applicationDelegate];
711 }
712 @end