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