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