diff options
Diffstat (limited to 'apple_remote/source/HIDRemoteControlDevice.m')
-rw-r--r-- | apple_remote/source/HIDRemoteControlDevice.m | 535 |
1 files changed, 0 insertions, 535 deletions
diff --git a/apple_remote/source/HIDRemoteControlDevice.m b/apple_remote/source/HIDRemoteControlDevice.m deleted file mode 100644 index a42a91419754..000000000000 --- a/apple_remote/source/HIDRemoteControlDevice.m +++ /dev/null @@ -1,535 +0,0 @@ -/***************************************************************************** - * HIDRemoteControlDevice.m - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Code modified and adapted to OpenOffice.org - * by Eric Bachard on 11.08.2008 under the same license - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import "HIDRemoteControlDevice.h" - -#import <mach/mach.h> -#import <mach/mach_error.h> -#import <IOKit/IOKitLib.h> -#import <IOKit/IOCFPlugIn.h> -#import <IOKit/hid/IOHIDKeys.h> -#import <Carbon/Carbon.h> - -@interface HIDRemoteControlDevice (PrivateMethods) -- (NSDictionary*) cookieToButtonMapping; // Creates the dictionary using the magics, depending on the remote -- (IOHIDQueueInterface**) queue; -- (IOHIDDeviceInterface**) hidDeviceInterface; -- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues; -- (void) removeNotifcationObserver; -- (void) remoteControlAvailable:(NSNotification *)notification; - -@end - -@interface HIDRemoteControlDevice (IOKitMethods) -+ (io_object_t) findRemoteDevice; -- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice; -- (BOOL) initializeCookies; -- (BOOL) openDevice; -@end - -@implementation HIDRemoteControlDevice - -+ (const char*) remoteControlDeviceName { - return ""; -} - -+ (BOOL) isRemoteAvailable { - io_object_t hidDevice = [self findRemoteDevice]; - if (hidDevice != 0) { - IOObjectRelease(hidDevice); - return YES; - } else { - return NO; - } -} - -- (id) initWithDelegate: (id) _remoteControlDelegate { - if ([[self class] isRemoteAvailable] == NO) return nil; - - if ( (self = [super initWithDelegate: _remoteControlDelegate]) ) { - openInExclusiveMode = YES; - queue = NULL; - hidDeviceInterface = NULL; - cookieToButtonMapping = [[NSMutableDictionary alloc] init]; - - [self setCookieMappingInDictionary: cookieToButtonMapping]; - - NSEnumerator* enumerator = [cookieToButtonMapping objectEnumerator]; - NSNumber* identifier; - supportedButtonEvents = 0; - while( (identifier = [enumerator nextObject]) ) { - supportedButtonEvents |= [identifier intValue]; - } - - fixSecureEventInputBug = [[NSUserDefaults standardUserDefaults] boolForKey: @"remoteControlWrapperFixSecureEventInputBug"]; - } - - return self; -} - -- (void) dealloc { - [self removeNotifcationObserver]; - [self stopListening:self]; - [cookieToButtonMapping release]; - [super dealloc]; -} - -- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { - [delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self]; -} - -- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMap { - (void)cookieToButtonMap; -} - -- (int) remoteIdSwitchCookie { - return 0; -} - -- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { - return (supportedButtonEvents & identifier) == identifier; -} - -- (BOOL) isListeningToRemote { - return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL); -} - -- (void) setListeningToRemote: (BOOL) value { - if (value == NO) { - [self stopListening:self]; - } else { - [self startListening:self]; - } -} - -- (BOOL) isOpenInExclusiveMode { - return openInExclusiveMode; -} -- (void) setOpenInExclusiveMode: (BOOL) value { - openInExclusiveMode = value; -} - -- (BOOL) processesBacklog { - return processesBacklog; -} -- (void) setProcessesBacklog: (BOOL) value { - processesBacklog = value; -} - -- (void) startListening: (id) sender { - (void)sender; - if ([self isListeningToRemote]) return; - - // 4th July 2007 - // - // A security update in february of 2007 introduced an odd behavior. - // Whenever SecureEventInput is activated or deactivated the exclusive access - // to the remote control device is lost. This leads to very strange behavior where - // a press on the Menu button activates FrontRow while your app still gets the event. - // A great number of people have complained about this. - // - // Enabling the SecureEventInput and keeping it enabled does the trick. - // - // I'm pretty sure this is a kind of bug at Apple and I'm in contact with the responsible - // Apple Engineer. This solution is not a perfect one - I know. - // One of the side effects is that applications that listen for special global keyboard shortcuts (like Quicksilver) - // may get into problems as they no longer get the events. - // As there is no official Apple Remote API from Apple I also failed to open a technical incident on this. - // - // Note that there is a corresponding DisableSecureEventInput in the stopListening method below. - // - if ([self isOpenInExclusiveMode] && fixSecureEventInputBug) EnableSecureEventInput(); - - [self removeNotifcationObserver]; - - io_object_t hidDevice = [[self class] findRemoteDevice]; - if (hidDevice == 0) return; - - if ([self createInterfaceForDevice:hidDevice] == NULL) { - goto error; - } - - if ([self initializeCookies]==NO) { - goto error; - } - - if ([self openDevice]==NO) { - goto error; - } - // be KVO friendly - [self willChangeValueForKey:@"listeningToRemote"]; - [self didChangeValueForKey:@"listeningToRemote"]; - goto cleanup; - -error: - [self stopListening:self]; - DisableSecureEventInput(); - -cleanup: - IOObjectRelease(hidDevice); -} - -- (void) stopListening: (id) sender { - (void)sender; - if ([self isListeningToRemote]==NO) return; - - BOOL sendNotification = NO; - - if (eventSource != NULL) { - CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); - CFRelease(eventSource); - eventSource = NULL; - } - if (queue != NULL) { - (*queue)->stop(queue); - - //dispose of queue - (*queue)->dispose(queue); - - //release the queue we allocated - (*queue)->Release(queue); - - queue = NULL; - - sendNotification = YES; - } - - if (allCookies != nil) { - [allCookies autorelease]; - allCookies = nil; - } - - if (hidDeviceInterface != NULL) { - //close the device - (*hidDeviceInterface)->close(hidDeviceInterface); - - //release the interface - (*hidDeviceInterface)->Release(hidDeviceInterface); - - hidDeviceInterface = NULL; - } - - if ([self isOpenInExclusiveMode] && fixSecureEventInputBug) DisableSecureEventInput(); - - if ([self isOpenInExclusiveMode] && sendNotification) { - [[self class] sendFinishedNotifcationForAppIdentifier: nil]; - } - // be KVO friendly - [self willChangeValueForKey:@"listeningToRemote"]; - [self didChangeValueForKey:@"listeningToRemote"]; -} - -@end - -@implementation HIDRemoteControlDevice (PrivateMethods) - -- (IOHIDQueueInterface**) queue { - return queue; -} - -- (IOHIDDeviceInterface**) hidDeviceInterface { - return hidDeviceInterface; -} - - -- (NSDictionary*) cookieToButtonMapping { - return cookieToButtonMapping; -} - -- (NSString*) validCookieSubstring: (NSString*) cookieString { - if (cookieString == nil || [cookieString length] == 0) return nil; - NSEnumerator* keyEnum = [[self cookieToButtonMapping] keyEnumerator]; - NSString* key; - while( (key = [keyEnum nextObject]) ) { - NSRange range = [cookieString rangeOfString:key]; - if (range.location == 0) return key; - } - return nil; -} - -- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues { - /* - if (previousRemainingCookieString) { - cookieString = [previousRemainingCookieString stringByAppendingString: cookieString]; - NSLog(@"New cookie string is %@", cookieString); - [previousRemainingCookieString release], previousRemainingCookieString=nil; - }*/ - if (cookieString == nil || [cookieString length] == 0) return; - - NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString]; - if (buttonId != nil) { - switch ( (int)buttonId ) - { - case kMetallicRemote2009ButtonPlay: - case kMetallicRemote2009ButtonMiddlePlay: - buttonId = [NSNumber numberWithInt:kRemoteButtonPlay]; - break; - default: - break; - } - [self sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)]; - - } else { - // let's see if a number of events are stored in the cookie string. this does - // happen when the main thread is too busy to handle all incoming events in time. - NSString* subCookieString; - NSString* lastSubCookieString=nil; - while( (subCookieString = [self validCookieSubstring: cookieString]) ) { - cookieString = [cookieString substringFromIndex: [subCookieString length]]; - lastSubCookieString = subCookieString; - if (processesBacklog) [self handleEventWithCookieString: subCookieString sumOfValues:sumOfValues]; - } - if (processesBacklog == NO && lastSubCookieString != nil) { - // process the last event of the backlog and assume that the button is not pressed down any longer. - // The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be - // a button pressed down event while in reality the user has released it. - // NSLog(@"processing last event of backlog"); - [self handleEventWithCookieString: lastSubCookieString sumOfValues:0]; - } - if ([cookieString length] > 0) { - NSLog(@"Unknown button for cookiestring %@", cookieString); - } - } -} - -- (void) removeNotifcationObserver { - [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; -} - -- (void) remoteControlAvailable:(NSNotification *)notification { - (void)notification; - [self removeNotifcationObserver]; - [self startListening: self]; -} - -@end - -/* Callback method for the device queue -Will be called for any event of any type (cookie) to which we subscribe -*/ -static void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) { - (void)refcon; - (void)sender; - if ((intptr_t)target < 0) { - NSLog(@"QueueCallbackFunction called with invalid target!"); - return; - } - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - - HIDRemoteControlDevice* remote = (HIDRemoteControlDevice*)target; - IOHIDEventStruct event; - AbsoluteTime zeroTime = {0,0}; - NSMutableString* cookieString = [NSMutableString string]; - SInt32 sumOfValues = 0; - while (result == kIOReturnSuccess) - { - result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0); - if ( result != kIOReturnSuccess ) - continue; - - //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue); - - if (((int)event.elementCookie)!=5) { - sumOfValues+=event.value; - [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]]; - } - } - [remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues]; - - [pool release]; -} - -@implementation HIDRemoteControlDevice (IOKitMethods) - -- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice { - io_name_t className; - IOCFPlugInInterface** plugInInterface = NULL; - HRESULT plugInResult = S_OK; - SInt32 score = 0; - IOReturn ioReturnValue = kIOReturnSuccess; - - hidDeviceInterface = NULL; - - ioReturnValue = IOObjectGetClass(hidDevice, className); - - if (ioReturnValue != kIOReturnSuccess) { - NSLog(@"Error: Failed to get class name."); - return NULL; - } - - ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice, - kIOHIDDeviceUserClientTypeID, - kIOCFPlugInInterfaceID, - &plugInInterface, - &score); - if (ioReturnValue == kIOReturnSuccess) - { - //Call a method of the intermediate plug-in to create the device interface - plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface); - - if (plugInResult != S_OK) { - NSLog(@"Error: Couldn't create HID class device interface"); - } - // Release - if (plugInInterface) (*plugInInterface)->Release(plugInInterface); - } - return hidDeviceInterface; -} - -- (BOOL) initializeCookies { - IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface; - IOHIDElementCookie cookie; - long usage; - long usagePage; - id object; - NSArray* elements = nil; - NSDictionary* element; - IOReturn success; - - if (!handle || !(*handle)) return NO; - - // Copy all elements, since we're grabbing most of the elements - // for this device anyway, and thus, it's faster to iterate them - // ourselves. When grabbing only one or two elements, a matching - // dictionary should be passed in here instead of NULL. - success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements); - - if (success == kIOReturnSuccess) { - - [elements autorelease]; - /* - cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie)); - memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS); - */ - allCookies = [[NSMutableArray alloc] init]; - - NSEnumerator *elementsEnumerator = [elements objectEnumerator]; - - while ( (element = [elementsEnumerator nextObject]) ) { - //Get cookie - object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ]; - if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; - if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue; - cookie = (IOHIDElementCookie) [object longValue]; - - //Get usage - object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ]; - if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; - usage = [object longValue]; - - //Get usage page - object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ]; - if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; - usagePage = [object longValue]; - - [allCookies addObject: [NSNumber numberWithInt:(int)cookie]]; - } - } else { - return NO; - } - - return YES; -} - -- (BOOL) openDevice { - HRESULT result; - - IOHIDOptionsType openMode = kIOHIDOptionsTypeNone; - if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice; - IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode); - - if (ioReturnValue == KERN_SUCCESS) { - queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); - if (queue) { - result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost. - - IOHIDElementCookie cookie; - NSEnumerator *allCookiesEnumerator = [allCookies objectEnumerator]; - - while ( (cookie = (IOHIDElementCookie)[[allCookiesEnumerator nextObject] intValue]) ) { - (*queue)->addElement(queue, cookie, 0); - } - - // add callback for async events - ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource); - if (ioReturnValue == KERN_SUCCESS) { - ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL); - if (ioReturnValue == KERN_SUCCESS) { - CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); - - //start data delivery to queue - (*queue)->start(queue); - return YES; - } else { - NSLog(@"Error when setting event callback"); - } - } else { - NSLog(@"Error when creating async event source"); - } - } else { - NSLog(@"Error when opening device"); - } - } else if (ioReturnValue == kIOReturnExclusiveAccess) { - // the device is used exclusive by another application - - // 1. we register for the FINISHED_USING_REMOTE_CONTROL_NOTIFICATION notification - [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(remoteControlAvailable:) name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; - - // 2. send a distributed notification that we wanted to use the remote control - [[self class] sendRequestForRemoteControlNotification]; - } - return NO; -} - -+ (io_object_t) findRemoteDevice { - CFMutableDictionaryRef hidMatchDictionary = NULL; - IOReturn ioReturnValue = kIOReturnSuccess; - io_iterator_t hidObjectIterator = 0; - io_object_t hidDevice = 0; - - // Set up a matching dictionary to search the I/O Registry by class - // name for all HID class devices - hidMatchDictionary = IOServiceMatching([self remoteControlDeviceName]); - - // Now search I/O Registry for matching devices. - ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); - - if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { - hidDevice = IOIteratorNext(hidObjectIterator); - } - - // release the iterator - IOObjectRelease(hidObjectIterator); - - return hidDevice; -} - -@end - |