]> git.sesse.net Git - vlc/blob - modules/gui/macosx/AppleRemote.m
* some minor fixes (signed vs unsigned ints, removed an always ignored pragma, fixed...
[vlc] / modules / gui / macosx / AppleRemote.m
1 //
2 //  AppleRemote.m
3 //  AppleRemote
4 //
5 //  Created by Martin Kahr on 11.03.06.
6 //  Copyright 2006 martinkahr.com. All rights reserved.
7 //
8
9 #import "AppleRemote.h"
10
11 const char* AppleRemoteDeviceName = "AppleIRController";
12 const int REMOTE_SWITCH_COOKIE=19;
13
14 @implementation AppleRemote
15
16 - (id) init {   
17         if ( self == [super init] ) {
18                 openInExclusiveMode = YES;
19                 queue = NULL;
20                 hidDeviceInterface = NULL;
21                 cookieToButtonMapping = [[NSMutableDictionary alloc] init];
22                 
23                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Plus]      forKey:@"14_12_11_6_5_"];
24                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"14_13_11_6_5_"];           
25                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu]                     forKey:@"14_7_6_5_14_7_6_5_"];          
26                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay]                     forKey:@"14_8_6_5_14_8_6_5_"];
27                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight]            forKey:@"14_9_6_5_14_9_6_5_"];
28                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft]                     forKey:@"14_10_6_5_14_10_6_5_"];
29                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold]       forKey:@"14_6_5_4_2_"];
30                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold]        forKey:@"14_6_5_3_2_"];
31                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold]        forKey:@"14_6_5_14_6_5_"];
32                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep]       forKey:@"18_14_6_5_18_14_6_5_"];
33                 [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched]       forKey:@"19_"];         
34         }
35         
36         return self;
37 }
38
39 - (void) dealloc {
40         [self stopListening:self];
41         [cookieToButtonMapping release];
42         [super dealloc];
43 }
44
45 - (void) setRemoteId: (int) value {
46         remoteId = value;
47 }
48 - (int) remoteId {
49         return remoteId;
50 }
51
52 - (BOOL) isRemoteAvailable {    
53         io_object_t hidDevice = [self findAppleRemoteDevice];
54         if (hidDevice != 0) {
55                 IOObjectRelease(hidDevice);
56                 return YES;
57         } else {
58                 return NO;              
59         }
60 }
61
62 - (BOOL) isListeningToRemote {
63         return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL);     
64 }
65
66 - (void) setListeningToRemote: (BOOL) value {
67         if (value == NO) {
68                 [self stopListening:self];
69         } else {
70                 [self startListening:self];
71         }
72 }
73
74 - (void) setDelegate: (id) _delegate {
75         if ([_delegate respondsToSelector:@selector(appleRemoteButton:pressedDown:)]==NO) return;
76         
77         [_delegate retain];
78         [delegate release];
79         delegate = _delegate;
80 }
81 - (id) delegate {
82         return delegate;
83 }
84
85 - (BOOL) isOpenInExclusiveMode {
86         return openInExclusiveMode;
87 }
88 - (void) setOpenInExclusiveMode: (BOOL) value {
89         openInExclusiveMode = value;
90 }
91
92 - (IBAction) startListening: (id) sender {      
93         if ([self isListeningToRemote]) return;
94         
95         io_object_t hidDevice = [self findAppleRemoteDevice];
96         if (hidDevice == 0) return;
97         
98         if ([self createInterfaceForDevice:hidDevice] == NULL) {
99                 goto error;
100         }
101         
102         if ([self initializeCookies]==NO) {
103                 goto error;
104         }
105
106         if ([self openDevice]==NO) {
107                 goto error;
108         }
109         goto cleanup;
110         
111 error:
112         [self stopListening:self];
113         
114 cleanup:        
115         IOObjectRelease(hidDevice);
116 }
117
118 - (IBAction) stopListening: (id) sender {
119         if (queue != NULL) {
120                 (*queue)->stop(queue);          
121                 
122                 //dispose of queue
123                 (*queue)->dispose(queue);               
124                 
125                 //release the queue we allocated
126                 (*queue)->Release(queue);       
127                 
128                 queue = NULL;
129         }
130         
131         if (allCookies != nil) {
132                 [allCookies autorelease];
133                 allCookies = nil;
134         }
135         
136         if (hidDeviceInterface != NULL) {
137                 //close the device
138                 (*hidDeviceInterface)->close(hidDeviceInterface);
139                 
140                 //release the interface 
141                 (*hidDeviceInterface)->Release(hidDeviceInterface);
142                 
143                 hidDeviceInterface = NULL;              
144         }       
145 }
146
147 @end
148
149 @implementation AppleRemote (Singleton) 
150
151 static AppleRemote* sharedInstance=nil;
152
153 + (AppleRemote*) sharedRemote { 
154         @synchronized(self) {
155         if( sharedInstance == nil ) {
156             sharedInstance = [[self alloc] init];
157         }
158     }
159         return sharedInstance;
160 }
161 + (id)allocWithZone:(NSZone *)zone {
162     @synchronized(self) {
163         if (sharedInstance == nil) {
164             return [super allocWithZone:zone];
165         }
166     }   
167     return sharedInstance;
168 }
169 - (id)copyWithZone:(NSZone *)zone {
170     return self;
171 }
172 - (id)retain {
173     return self;
174 }
175 - (unsigned)retainCount {
176     return UINT_MAX;  //denotes an object that cannot be released
177 }
178 - (void)release {
179     //do nothing
180 }
181 - (id)autorelease {
182     return self;
183 }
184
185 @end
186
187 @implementation AppleRemote (PrivateMethods) 
188
189 - (IOHIDQueueInterface**) queue {
190         return queue;
191 }
192
193 - (IOHIDDeviceInterface**) hidDeviceInterface {
194         return hidDeviceInterface;
195 }
196
197
198 - (NSDictionary*) cookieToButtonMapping {
199         return cookieToButtonMapping;
200 }
201
202 - (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues {
203         NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString];
204         if (buttonId != nil) {
205                 if (delegate) {         
206                         [delegate appleRemoteButton:[buttonId intValue] pressedDown: (sumOfValues>0)];
207                 }               
208         } else {
209                 NSLog(@"Unknown button for cookiestring %@", cookieString);
210         }
211 }
212
213 @end
214
215 /*      Callback method for the device queue
216 Will be called for any event of any type (cookie) to which we subscribe
217 */
218 static void QueueCallbackFunction(void* target,  IOReturn result, void* refcon, void* sender) { 
219         AppleRemote* remote = (AppleRemote*)target;
220         
221         IOHIDEventStruct event; 
222         AbsoluteTime     zeroTime = {0,0};
223         NSMutableString* cookieString = [NSMutableString string];
224         SInt32                   sumOfValues = 0;
225         while (result == kIOReturnSuccess)
226         {
227                 result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0);          
228                 if ( result != kIOReturnSuccess )
229                         continue;
230                 
231                 if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie) {
232                         [remote setRemoteId: event.value];
233                         [remote handleEventWithCookieString: @"19_" sumOfValues: 0];
234                 } else {
235                         sumOfValues+=event.value;
236                         [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]];                                    
237                 }
238                 
239                 //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue);              
240         }
241         
242         [remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues];    
243         
244 }
245
246 @implementation AppleRemote (IOKitMethods)
247
248 - (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice {
249         io_name_t                               className;
250         IOCFPlugInInterface**   plugInInterface = NULL;
251         HRESULT                                 plugInResult = S_OK;
252         SInt32                                  score = 0;
253         IOReturn                                ioReturnValue = kIOReturnSuccess;
254         
255         hidDeviceInterface = NULL;
256         
257         ioReturnValue = IOObjectGetClass(hidDevice, className);
258         
259         if (ioReturnValue != kIOReturnSuccess) {
260                 NSLog(@"Error: Failed to get class name.");
261                 return NULL;
262         }
263         
264         ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice,
265                                                                                                           kIOHIDDeviceUserClientTypeID,
266                                                                                                           kIOCFPlugInInterfaceID,
267                                                                                                           &plugInInterface,
268                                                                                                           &score);
269         if (ioReturnValue == kIOReturnSuccess)
270         {
271                 //Call a method of the intermediate plug-in to create the device interface
272                 plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface);
273                 
274                 if (plugInResult != S_OK) {
275                         NSLog(@"Error: Couldn't create HID class device interface");
276                 }
277                 // Release
278                 if (plugInInterface) (*plugInInterface)->Release(plugInInterface);
279         }
280         return hidDeviceInterface;
281 }
282
283 - (io_object_t) findAppleRemoteDevice {
284         CFMutableDictionaryRef hidMatchDictionary = NULL;
285         IOReturn ioReturnValue = kIOReturnSuccess;      
286         io_iterator_t hidObjectIterator = 0;
287         io_object_t     hidDevice = 0;
288         
289         // Set up a matching dictionary to search the I/O Registry by class
290         // name for all HID class devices
291         hidMatchDictionary = IOServiceMatching(AppleRemoteDeviceName);
292         
293         // Now search I/O Registry for matching devices.
294         ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator);
295         
296         if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) {
297                 hidDevice = IOIteratorNext(hidObjectIterator);
298         }
299         
300         // release the iterator
301         IOObjectRelease(hidObjectIterator);
302         
303         return hidDevice;
304 }
305
306 - (BOOL) initializeCookies {
307         IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
308         IOHIDElementCookie              cookie;
309         long                                    usage;
310         long                                    usagePage;
311         id                                              object;
312         NSArray*                                elements = nil;
313         NSDictionary*                   element;
314         IOReturn success;
315         
316         if (!handle || !(*handle)) return NO;
317         
318         // Copy all elements, since we're grabbing most of the elements
319         // for this device anyway, and thus, it's faster to iterate them
320         // ourselves. When grabbing only one or two elements, a matching
321         // dictionary should be passed in here instead of NULL.
322         success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements);
323         
324         if (success == kIOReturnSuccess) {
325                 
326                 [elements autorelease];         
327                 /*
328                 cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie)); 
329                 memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS);
330                 */
331                 allCookies = [[NSMutableArray alloc] init];
332                 unsigned int i;
333                 for (i=0; i< [elements count]; i++) {
334                         element = [elements objectAtIndex:i];
335                                                 
336                         //Get cookie
337                         object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ];
338                         if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
339                         if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue;
340                         cookie = (IOHIDElementCookie) [object longValue];
341                         
342                         //Get usage
343                         object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ];
344                         if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;                        
345                         usage = [object longValue];
346                         
347                         //Get usage page
348                         object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ];
349                         if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;                        
350                         usagePage = [object longValue];
351
352                         [allCookies addObject: [NSNumber numberWithInt:(int)cookie]];
353                 }
354         } else {
355                 return NO;
356         }
357         
358         return YES;
359 }
360
361 - (BOOL) openDevice {
362         HRESULT  result;
363         
364         IOHIDOptionsType openMode = kIOHIDOptionsTypeNone;
365         if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice;      
366         IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode);     
367         
368         if (ioReturnValue == KERN_SUCCESS) {
369                 queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
370                 if (queue) {
371                         result = (*queue)->create(queue, 0, 12);        //depth: maximum number of elements in queue before oldest elements in queue begin to be lost.
372
373                         unsigned int i=0;
374                         for(i=0; i<[allCookies count]; i++) {
375                                 IOHIDElementCookie cookie = (IOHIDElementCookie)[[allCookies objectAtIndex:i] intValue];
376                                 (*queue)->addElement(queue, cookie, 0);
377                         }
378                                                                           
379                         // add callback for async events
380                         CFRunLoopSourceRef eventSource;
381                         ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource);
382                         if (ioReturnValue == KERN_SUCCESS) {
383                                 ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL);
384                                 if (ioReturnValue == KERN_SUCCESS) {
385                                         CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);                                  
386                                         //start data delivery to queue
387                                         (*queue)->start(queue); 
388                                         return YES;
389                                 } else {
390                                         NSLog(@"Error when setting event callout");
391                                 }
392                         } else {
393                                 NSLog(@"Error when creating async event source");
394                         }
395                 } else {
396                         NSLog(@"Error when opening device");
397                 }
398         }
399         return NO;                              
400 }
401
402 @end