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