]> git.sesse.net Git - vlc/blob - modules/gui/macosx/AppleRemote.m
macosx intf: another strdup memleak
[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-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 (queue != NULL) {
235         (*queue)->stop(queue);
236
237         //dispose of queue
238         (*queue)->dispose(queue);
239
240         //release the queue we allocated
241         (*queue)->Release(queue);
242
243         queue = NULL;
244     }
245
246     if (allCookies != nil) {
247         [allCookies autorelease];
248         allCookies = nil;
249     }
250
251     if (hidDeviceInterface != NULL) {
252         //close the device
253         (*hidDeviceInterface)->close(hidDeviceInterface);
254
255         //release the interface
256         (*hidDeviceInterface)->Release(hidDeviceInterface);
257
258         hidDeviceInterface = NULL;
259     }
260 }
261
262 @end
263
264 @implementation AppleRemote (Singleton)
265
266 static AppleRemote* sharedInstance=nil;
267
268 + (AppleRemote*) sharedRemote {
269     @synchronized(self) {
270         if (sharedInstance == nil) {
271             sharedInstance = [[self alloc] init];
272         }
273     }
274     return sharedInstance;
275 }
276 + (id)allocWithZone:(NSZone *)zone {
277     @synchronized(self) {
278         if (sharedInstance == nil) {
279             return [super allocWithZone:zone];
280         }
281     }
282     return sharedInstance;
283 }
284 - (id)copyWithZone:(NSZone *)zone {
285     return self;
286 }
287 - (id)retain {
288     return self;
289 }
290 - (NSUInteger)retainCount {
291     return UINT_MAX;  //denotes an object that cannot be released
292 }
293 - (void)release {
294     //do nothing
295 }
296 - (id)autorelease {
297     return self;
298 }
299
300 @end
301
302 @implementation AppleRemote (PrivateMethods)
303
304 - (void) setRemoteId: (int) value {
305     remoteId = value;
306 }
307
308 - (IOHIDQueueInterface**) queue {
309     return queue;
310 }
311
312 - (IOHIDDeviceInterface**) hidDeviceInterface {
313     return hidDeviceInterface;
314 }
315
316
317 - (NSDictionary*) cookieToButtonMapping {
318     return cookieToButtonMapping;
319 }
320
321 - (NSString*) validCookieSubstring: (NSString*) cookieString {
322     if (cookieString == nil || [cookieString length] == 0) return nil;
323     NSEnumerator* keyEnum = [[self cookieToButtonMapping] keyEnumerator];
324     NSString* key;
325     while(key = [keyEnum nextObject]) {
326         NSRange range = [cookieString rangeOfString:key];
327         if (range.location == 0) return key;
328     }
329     return nil;
330 }
331
332 - (void) sendSimulatedPlusMinusEvent: (id) time {
333     BOOL startSimulateHold = NO;
334     AppleRemoteEventIdentifier event = lastPlusMinusEvent;
335     @synchronized(self) {
336         startSimulateHold = (lastPlusMinusEvent>0 && lastPlusMinusEventTime == [time doubleValue]);
337     }
338     if (startSimulateHold) {
339         lastEventSimulatedHold = YES;
340         event = (event==kRemoteButtonVolume_Plus) ? kRemoteButtonVolume_Plus_Hold : kRemoteButtonVolume_Minus_Hold;
341         [delegate appleRemoteButton:event pressedDown: YES clickCount: 1];
342     }
343 }
344
345 - (void) sendRemoteButtonEvent: (AppleRemoteEventIdentifier) event pressedDown: (BOOL) pressedDown {
346     if (delegate) {
347         if (simulatePlusMinusHold) {
348             if (event == kRemoteButtonVolume_Plus || event == kRemoteButtonVolume_Minus) {
349                 if (pressedDown) {
350                     lastPlusMinusEvent = event;
351                     lastPlusMinusEventTime = [NSDate timeIntervalSinceReferenceDate];
352                     [self performSelector:@selector(sendSimulatedPlusMinusEvent:)
353                                withObject:[NSNumber numberWithDouble:lastPlusMinusEventTime]
354                                afterDelay:HOLD_RECOGNITION_TIME_INTERVAL];
355                     return;
356                 } else {
357                     if (lastEventSimulatedHold) {
358                         event = (event==kRemoteButtonVolume_Plus) ? kRemoteButtonVolume_Plus_Hold : kRemoteButtonVolume_Minus_Hold;
359                         lastPlusMinusEvent = 0;
360                         lastEventSimulatedHold = NO;
361                     } else {
362                         @synchronized(self) {
363                             lastPlusMinusEvent = 0;
364                         }
365                         pressedDown = YES;
366                     }
367                 }
368             }
369         }
370
371         if (([self clickCountEnabledButtons] & event) == event) {
372             if (pressedDown==NO && (event == kRemoteButtonVolume_Minus || event == kRemoteButtonVolume_Plus)) {
373                 return; // this one is triggered automatically by the handler
374             }
375             NSNumber* eventNumber;
376             NSNumber* timeNumber;
377             @synchronized(self) {
378                 lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate];
379                 if (lastClickCountEvent == event) {
380                     eventClickCount = eventClickCount + 1;
381                 } else {
382                     eventClickCount = 1;
383                 }
384                 lastClickCountEvent = event;
385                 timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime];
386                 eventNumber= [NSNumber numberWithUnsignedInt:event];
387             }
388             [self performSelector: @selector(executeClickCountEvent:)
389                        withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil]
390                        afterDelay: maxClickTimeDifference];
391         } else {
392             [delegate appleRemoteButton:event pressedDown: pressedDown clickCount:1];
393         }
394     }
395 }
396
397 - (void) executeClickCountEvent: (NSArray*) values {
398     AppleRemoteEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue];
399     NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue];
400
401     BOOL finishedClicking = NO;
402     int finalClickCount = eventClickCount;
403
404     @synchronized(self) {
405         finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime);
406         if (finishedClicking) eventClickCount = 0;
407     }
408
409     if (finishedClicking) {
410         [delegate appleRemoteButton:event pressedDown: YES clickCount:finalClickCount];
411         if ([self simulatesPlusMinusHold]==NO && (event == kRemoteButtonVolume_Minus || event == kRemoteButtonVolume_Plus)) {
412             // trigger a button release event, too
413             [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]];
414             [delegate appleRemoteButton:event pressedDown: NO clickCount:finalClickCount];
415         }
416     }
417
418 }
419
420 - (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues {
421     /*
422     if (previousRemainingCookieString) {
423         cookieString = [previousRemainingCookieString stringByAppendingString: cookieString];
424         NSLog(@"New cookie string is %@", cookieString);
425         [previousRemainingCookieString release], previousRemainingCookieString=nil;
426     }*/
427     if (cookieString == nil || [cookieString length] == 0) return;
428     NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString];
429     if (buttonId != nil) {
430         [self sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)];
431     } else {
432         // let's see if a number of events are stored in the cookie string. this does
433         // happen when the main thread is too busy to handle all incoming events in time.
434         NSString* subCookieString;
435         NSString* lastSubCookieString=nil;
436         while(subCookieString = [self validCookieSubstring: cookieString]) {
437             cookieString = [cookieString substringFromIndex: [subCookieString length]];
438             lastSubCookieString = subCookieString;
439             if (processesBacklog) [self handleEventWithCookieString: subCookieString sumOfValues:sumOfValues];
440         }
441         if (processesBacklog == NO && lastSubCookieString != nil) {
442             // process the last event of the backlog and assume that the button is not pressed down any longer.
443             // The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be
444             // a button pressed down event while in reality the user has released it.
445             // NSLog(@"processing last event of backlog");
446             [self handleEventWithCookieString: lastSubCookieString sumOfValues:0];
447         }
448         if ([cookieString length] > 0) {
449             NSLog(@"Unknown button for cookiestring %@", cookieString);
450         }
451     }
452 }
453
454 @end
455
456 /*  Callback method for the device queue
457 Will be called for any event of any type (cookie) to which we subscribe
458 */
459 static void QueueCallbackFunction(void* target,  IOReturn result, void* refcon, void* sender) {
460     AppleRemote* remote = (AppleRemote*)target;
461
462     IOHIDEventStruct event;
463     AbsoluteTime     zeroTime = {0,0};
464     NSMutableString* cookieString = [NSMutableString string];
465     SInt32           sumOfValues = 0;
466     while (result == kIOReturnSuccess)
467     {
468         result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0);
469         if ( result != kIOReturnSuccess )
470             continue;
471
472         //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue);
473
474         if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie) {
475             [remote setRemoteId: event.value];
476             [remote handleEventWithCookieString: @"19_" sumOfValues: 0];
477         } else {
478             if (((int)event.elementCookie)!=5) {
479                 sumOfValues+=event.value;
480                 [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]];
481             }
482         }
483     }
484
485     [remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues];
486 }
487
488 @implementation AppleRemote (IOKitMethods)
489
490 - (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice {
491     io_name_t               className;
492     IOCFPlugInInterface**   plugInInterface = NULL;
493     HRESULT                 plugInResult = S_OK;
494     SInt32                  score = 0;
495     IOReturn                ioReturnValue = kIOReturnSuccess;
496
497     hidDeviceInterface = NULL;
498
499     ioReturnValue = IOObjectGetClass(hidDevice, className);
500
501     if (ioReturnValue != kIOReturnSuccess) {
502         NSLog(@"Error: Failed to get class name.");
503         return NULL;
504     }
505
506     ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice,
507                                                       kIOHIDDeviceUserClientTypeID,
508                                                       kIOCFPlugInInterfaceID,
509                                                       &plugInInterface,
510                                                       &score);
511     if (ioReturnValue == kIOReturnSuccess)
512     {
513         //Call a method of the intermediate plug-in to create the device interface
514         plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface);
515
516         if (plugInResult != S_OK) {
517             NSLog(@"Error: Couldn't create HID class device interface");
518         }
519         // Release
520         if (plugInInterface) (*plugInInterface)->Release(plugInInterface);
521     }
522     return hidDeviceInterface;
523 }
524
525 - (io_object_t) findAppleRemoteDevice {
526     CFMutableDictionaryRef hidMatchDictionary = NULL;
527     IOReturn ioReturnValue = kIOReturnSuccess;
528     io_iterator_t hidObjectIterator = 0;
529     io_object_t hidDevice = 0;
530
531     // Set up a matching dictionary to search the I/O Registry by class
532     // name for all HID class devices
533     hidMatchDictionary = IOServiceMatching(AppleRemoteDeviceName);
534
535     // Now search I/O Registry for matching devices.
536     ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator);
537
538     if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) {
539         hidDevice = IOIteratorNext(hidObjectIterator);
540     }
541
542     // release the iterator
543     IOObjectRelease(hidObjectIterator);
544
545     return hidDevice;
546 }
547
548 - (BOOL) initializeCookies {
549     IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
550     IOHIDElementCookie      cookie;
551     long                    usage;
552     long                    usagePage;
553     id                      object;
554     NSArray*                elements = nil;
555     NSDictionary*           element;
556     IOReturn success;
557
558     if (!handle || !(*handle)) return NO;
559
560     /* Copy all elements, since we're grabbing most of the elements
561      * for this device anyway, and thus, it's faster to iterate them
562      * ourselves. When grabbing only one or two elements, a matching
563      * dictionary should be passed in here instead of NULL. */
564     success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements);
565
566     if (success == kIOReturnSuccess) {
567
568         [elements autorelease];
569         /*
570         cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie));
571         memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS);
572         */
573         allCookies = [[NSMutableArray alloc] init];
574         int i;
575         for (i=0; i< [elements count]; i++) {
576             element = [elements objectAtIndex:i];
577
578             //Get cookie
579             object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ];
580             if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
581             if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue;
582             cookie = (IOHIDElementCookie) [object longValue];
583
584             //Get usage
585             object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ];
586             if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
587             usage = [object longValue];
588
589             //Get usage page
590             object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ];
591             if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
592             usagePage = [object longValue];
593
594             [allCookies addObject: [NSNumber numberWithInt:(int)cookie]];
595         }
596     } else {
597         return NO;
598     }
599
600     return YES;
601 }
602
603 - (BOOL) openDevice {
604     HRESULT  result;
605
606     IOHIDOptionsType openMode = kIOHIDOptionsTypeNone;
607     if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice;
608     IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode);
609
610     if (ioReturnValue == KERN_SUCCESS) {
611         queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
612         if (queue) {
613             result = (*queue)->create(queue, 0, 12);    //depth: maximum number of elements in queue before oldest elements in queue begin to be lost.
614
615             int i=0;
616             for(i=0; i<[allCookies count]; i++) {
617                 IOHIDElementCookie cookie = (IOHIDElementCookie)[[allCookies objectAtIndex:i] intValue];
618                 (*queue)->addElement(queue, cookie, 0);
619             }
620
621             // add callback for async events
622             CFRunLoopSourceRef eventSource;
623             ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource);
624             if (ioReturnValue == KERN_SUCCESS) {
625                 ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL);
626                 if (ioReturnValue == KERN_SUCCESS) {
627                     CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
628                     //start data delivery to queue
629                     (*queue)->start(queue);
630                     return YES;
631                 } else {
632                     NSLog(@"Error when setting event callout");
633                 }
634             } else {
635                 NSLog(@"Error when creating async event source");
636             }
637         } else {
638             NSLog(@"Error when opening device");
639         }
640     }
641     return NO;
642 }
643
644 @end
645
646 @implementation AppleRemoteApplicationDelegate
647
648 - (id) initWithApplicationDelegate: (id) delegate {
649     if (self = [super init]) {
650         applicationDelegate = [delegate retain];
651     }
652     return self;
653 }
654
655 - (void) dealloc {
656     NSLog(@"Dealloc");
657     [applicationDelegate release];
658     [super dealloc];
659 }
660
661 - (id) applicationDelegate {
662     return applicationDelegate;
663 }
664
665 - (void)applicationWillBecomeActive:(NSNotification *)aNotification {
666     if ([applicationDelegate respondsToSelector: @selector(applicationWillBecomeActive:)]) {
667         [applicationDelegate applicationWillBecomeActive: aNotification];
668     }
669 }
670 - (void)applicationDidBecomeActive:(NSNotification *)aNotification {
671     [[AppleRemote sharedRemote] setListeningToRemote: YES];
672
673     if ([applicationDelegate respondsToSelector: @selector(applicationDidBecomeActive:)]) {
674         [applicationDelegate applicationDidBecomeActive: aNotification];
675     }
676 }
677 - (void)applicationWillResignActive:(NSNotification *)aNotification {
678     [[AppleRemote sharedRemote] setListeningToRemote: NO];
679
680     if ([applicationDelegate respondsToSelector: @selector(applicationWillResignActive:)]) {
681         [applicationDelegate applicationWillResignActive: aNotification];
682     }
683 }
684 - (void)applicationDidResignActive:(NSNotification *)aNotification {
685     if ([applicationDelegate respondsToSelector: @selector(applicationDidResignActive:)]) {
686         [applicationDelegate applicationDidResignActive: aNotification];
687     }
688 }
689
690 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
691     NSMethodSignature* signature = [super methodSignatureForSelector: aSelector];
692     if (signature == nil && applicationDelegate != nil) {
693         signature = [applicationDelegate methodSignatureForSelector: aSelector];
694     }
695     return signature;
696 }
697
698 - (void)forwardInvocation:(NSInvocation *)invocation {
699     SEL aSelector = [invocation selector];
700
701     if (applicationDelegate==nil || [applicationDelegate respondsToSelector:aSelector]==NO) {
702         [super forwardInvocation: invocation];
703         return;
704     }
705
706     [invocation invokeWithTarget:applicationDelegate];
707 }
708 @end