1*cdf0e10cSrcweir/***************************************************************************** 2*cdf0e10cSrcweir * HIDRemoteControlDevice.m 3*cdf0e10cSrcweir * RemoteControlWrapper 4*cdf0e10cSrcweir * 5*cdf0e10cSrcweir * Created by Martin Kahr on 11.03.06 under a MIT-style license. 6*cdf0e10cSrcweir * Copyright (c) 2006 martinkahr.com. All rights reserved. 7*cdf0e10cSrcweir * 8*cdf0e10cSrcweir * Code modified and adapted to OpenOffice.org 9*cdf0e10cSrcweir * by Eric Bachard on 11.08.2008 under the same license 10*cdf0e10cSrcweir * 11*cdf0e10cSrcweir * Permission is hereby granted, free of charge, to any person obtaining a 12*cdf0e10cSrcweir * copy of this software and associated documentation files (the "Software"), 13*cdf0e10cSrcweir * to deal in the Software without restriction, including without limitation 14*cdf0e10cSrcweir * the rights to use, copy, modify, merge, publish, distribute, sublicense, 15*cdf0e10cSrcweir * and/or sell copies of the Software, and to permit persons to whom the 16*cdf0e10cSrcweir * Software is furnished to do so, subject to the following conditions: 17*cdf0e10cSrcweir * 18*cdf0e10cSrcweir * The above copyright notice and this permission notice shall be included 19*cdf0e10cSrcweir * in all copies or substantial portions of the Software. 20*cdf0e10cSrcweir * 21*cdf0e10cSrcweir * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22*cdf0e10cSrcweir * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23*cdf0e10cSrcweir * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 24*cdf0e10cSrcweir * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25*cdf0e10cSrcweir * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26*cdf0e10cSrcweir * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27*cdf0e10cSrcweir * THE SOFTWARE. 28*cdf0e10cSrcweir * 29*cdf0e10cSrcweir *****************************************************************************/ 30*cdf0e10cSrcweir 31*cdf0e10cSrcweir#import "HIDRemoteControlDevice.h" 32*cdf0e10cSrcweir 33*cdf0e10cSrcweir#import <mach/mach.h> 34*cdf0e10cSrcweir#import <mach/mach_error.h> 35*cdf0e10cSrcweir#import <IOKit/IOKitLib.h> 36*cdf0e10cSrcweir#import <IOKit/IOCFPlugIn.h> 37*cdf0e10cSrcweir#import <IOKit/hid/IOHIDKeys.h> 38*cdf0e10cSrcweir#import <Carbon/Carbon.h> 39*cdf0e10cSrcweir 40*cdf0e10cSrcweir@interface HIDRemoteControlDevice (PrivateMethods) 41*cdf0e10cSrcweir- (NSDictionary*) cookieToButtonMapping; // Creates the dictionary using the magics, depending on the remote 42*cdf0e10cSrcweir- (IOHIDQueueInterface**) queue; 43*cdf0e10cSrcweir- (IOHIDDeviceInterface**) hidDeviceInterface; 44*cdf0e10cSrcweir- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues; 45*cdf0e10cSrcweir- (void) removeNotifcationObserver; 46*cdf0e10cSrcweir- (void) remoteControlAvailable:(NSNotification *)notification; 47*cdf0e10cSrcweir 48*cdf0e10cSrcweir@end 49*cdf0e10cSrcweir 50*cdf0e10cSrcweir@interface HIDRemoteControlDevice (IOKitMethods) 51*cdf0e10cSrcweir+ (io_object_t) findRemoteDevice; 52*cdf0e10cSrcweir- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice; 53*cdf0e10cSrcweir- (BOOL) initializeCookies; 54*cdf0e10cSrcweir- (BOOL) openDevice; 55*cdf0e10cSrcweir@end 56*cdf0e10cSrcweir 57*cdf0e10cSrcweir@implementation HIDRemoteControlDevice 58*cdf0e10cSrcweir 59*cdf0e10cSrcweir+ (const char*) remoteControlDeviceName { 60*cdf0e10cSrcweir return ""; 61*cdf0e10cSrcweir} 62*cdf0e10cSrcweir 63*cdf0e10cSrcweir+ (BOOL) isRemoteAvailable { 64*cdf0e10cSrcweir io_object_t hidDevice = [self findRemoteDevice]; 65*cdf0e10cSrcweir if (hidDevice != 0) { 66*cdf0e10cSrcweir IOObjectRelease(hidDevice); 67*cdf0e10cSrcweir return YES; 68*cdf0e10cSrcweir } else { 69*cdf0e10cSrcweir return NO; 70*cdf0e10cSrcweir } 71*cdf0e10cSrcweir} 72*cdf0e10cSrcweir 73*cdf0e10cSrcweir- (id) initWithDelegate: (id) _remoteControlDelegate { 74*cdf0e10cSrcweir if ([[self class] isRemoteAvailable] == NO) return nil; 75*cdf0e10cSrcweir 76*cdf0e10cSrcweir if ( (self = [super initWithDelegate: _remoteControlDelegate]) ) { 77*cdf0e10cSrcweir openInExclusiveMode = YES; 78*cdf0e10cSrcweir queue = NULL; 79*cdf0e10cSrcweir hidDeviceInterface = NULL; 80*cdf0e10cSrcweir cookieToButtonMapping = [[NSMutableDictionary alloc] init]; 81*cdf0e10cSrcweir 82*cdf0e10cSrcweir [self setCookieMappingInDictionary: cookieToButtonMapping]; 83*cdf0e10cSrcweir 84*cdf0e10cSrcweir NSEnumerator* enumerator = [cookieToButtonMapping objectEnumerator]; 85*cdf0e10cSrcweir NSNumber* identifier; 86*cdf0e10cSrcweir supportedButtonEvents = 0; 87*cdf0e10cSrcweir while( (identifier = [enumerator nextObject]) ) { 88*cdf0e10cSrcweir supportedButtonEvents |= [identifier intValue]; 89*cdf0e10cSrcweir } 90*cdf0e10cSrcweir 91*cdf0e10cSrcweir fixSecureEventInputBug = [[NSUserDefaults standardUserDefaults] boolForKey: @"remoteControlWrapperFixSecureEventInputBug"]; 92*cdf0e10cSrcweir } 93*cdf0e10cSrcweir 94*cdf0e10cSrcweir return self; 95*cdf0e10cSrcweir} 96*cdf0e10cSrcweir 97*cdf0e10cSrcweir- (void) dealloc { 98*cdf0e10cSrcweir [self removeNotifcationObserver]; 99*cdf0e10cSrcweir [self stopListening:self]; 100*cdf0e10cSrcweir [cookieToButtonMapping release]; 101*cdf0e10cSrcweir [super dealloc]; 102*cdf0e10cSrcweir} 103*cdf0e10cSrcweir 104*cdf0e10cSrcweir- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { 105*cdf0e10cSrcweir [delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self]; 106*cdf0e10cSrcweir} 107*cdf0e10cSrcweir 108*cdf0e10cSrcweir- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMapping { 109*cdf0e10cSrcweir} 110*cdf0e10cSrcweir- (int) remoteIdSwitchCookie { 111*cdf0e10cSrcweir return 0; 112*cdf0e10cSrcweir} 113*cdf0e10cSrcweir 114*cdf0e10cSrcweir- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { 115*cdf0e10cSrcweir return (supportedButtonEvents & identifier) == identifier; 116*cdf0e10cSrcweir} 117*cdf0e10cSrcweir 118*cdf0e10cSrcweir- (BOOL) isListeningToRemote { 119*cdf0e10cSrcweir return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL); 120*cdf0e10cSrcweir} 121*cdf0e10cSrcweir 122*cdf0e10cSrcweir- (void) setListeningToRemote: (BOOL) value { 123*cdf0e10cSrcweir if (value == NO) { 124*cdf0e10cSrcweir [self stopListening:self]; 125*cdf0e10cSrcweir } else { 126*cdf0e10cSrcweir [self startListening:self]; 127*cdf0e10cSrcweir } 128*cdf0e10cSrcweir} 129*cdf0e10cSrcweir 130*cdf0e10cSrcweir- (BOOL) isOpenInExclusiveMode { 131*cdf0e10cSrcweir return openInExclusiveMode; 132*cdf0e10cSrcweir} 133*cdf0e10cSrcweir- (void) setOpenInExclusiveMode: (BOOL) value { 134*cdf0e10cSrcweir openInExclusiveMode = value; 135*cdf0e10cSrcweir} 136*cdf0e10cSrcweir 137*cdf0e10cSrcweir- (BOOL) processesBacklog { 138*cdf0e10cSrcweir return processesBacklog; 139*cdf0e10cSrcweir} 140*cdf0e10cSrcweir- (void) setProcessesBacklog: (BOOL) value { 141*cdf0e10cSrcweir processesBacklog = value; 142*cdf0e10cSrcweir} 143*cdf0e10cSrcweir 144*cdf0e10cSrcweir- (void) startListening: (id) sender { 145*cdf0e10cSrcweir if ([self isListeningToRemote]) return; 146*cdf0e10cSrcweir 147*cdf0e10cSrcweir // 4th July 2007 148*cdf0e10cSrcweir // 149*cdf0e10cSrcweir // A security update in february of 2007 introduced an odd behavior. 150*cdf0e10cSrcweir // Whenever SecureEventInput is activated or deactivated the exclusive access 151*cdf0e10cSrcweir // to the remote control device is lost. This leads to very strange behavior where 152*cdf0e10cSrcweir // a press on the Menu button activates FrontRow while your app still gets the event. 153*cdf0e10cSrcweir // A great number of people have complained about this. 154*cdf0e10cSrcweir // 155*cdf0e10cSrcweir // Enabling the SecureEventInput and keeping it enabled does the trick. 156*cdf0e10cSrcweir // 157*cdf0e10cSrcweir // I'm pretty sure this is a kind of bug at Apple and I'm in contact with the responsible 158*cdf0e10cSrcweir // Apple Engineer. This solution is not a perfect one - I know. 159*cdf0e10cSrcweir // One of the side effects is that applications that listen for special global keyboard shortcuts (like Quicksilver) 160*cdf0e10cSrcweir // may get into problems as they no longer get the events. 161*cdf0e10cSrcweir // As there is no official Apple Remote API from Apple I also failed to open a technical incident on this. 162*cdf0e10cSrcweir // 163*cdf0e10cSrcweir // Note that there is a corresponding DisableSecureEventInput in the stopListening method below. 164*cdf0e10cSrcweir // 165*cdf0e10cSrcweir if ([self isOpenInExclusiveMode] && fixSecureEventInputBug) EnableSecureEventInput(); 166*cdf0e10cSrcweir 167*cdf0e10cSrcweir [self removeNotifcationObserver]; 168*cdf0e10cSrcweir 169*cdf0e10cSrcweir io_object_t hidDevice = [[self class] findRemoteDevice]; 170*cdf0e10cSrcweir if (hidDevice == 0) return; 171*cdf0e10cSrcweir 172*cdf0e10cSrcweir if ([self createInterfaceForDevice:hidDevice] == NULL) { 173*cdf0e10cSrcweir goto error; 174*cdf0e10cSrcweir } 175*cdf0e10cSrcweir 176*cdf0e10cSrcweir if ([self initializeCookies]==NO) { 177*cdf0e10cSrcweir goto error; 178*cdf0e10cSrcweir } 179*cdf0e10cSrcweir 180*cdf0e10cSrcweir if ([self openDevice]==NO) { 181*cdf0e10cSrcweir goto error; 182*cdf0e10cSrcweir } 183*cdf0e10cSrcweir // be KVO friendly 184*cdf0e10cSrcweir [self willChangeValueForKey:@"listeningToRemote"]; 185*cdf0e10cSrcweir [self didChangeValueForKey:@"listeningToRemote"]; 186*cdf0e10cSrcweir goto cleanup; 187*cdf0e10cSrcweir 188*cdf0e10cSrcweirerror: 189*cdf0e10cSrcweir [self stopListening:self]; 190*cdf0e10cSrcweir DisableSecureEventInput(); 191*cdf0e10cSrcweir 192*cdf0e10cSrcweircleanup: 193*cdf0e10cSrcweir IOObjectRelease(hidDevice); 194*cdf0e10cSrcweir} 195*cdf0e10cSrcweir 196*cdf0e10cSrcweir- (void) stopListening: (id) sender { 197*cdf0e10cSrcweir if ([self isListeningToRemote]==NO) return; 198*cdf0e10cSrcweir 199*cdf0e10cSrcweir BOOL sendNotification = NO; 200*cdf0e10cSrcweir 201*cdf0e10cSrcweir if (eventSource != NULL) { 202*cdf0e10cSrcweir CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); 203*cdf0e10cSrcweir CFRelease(eventSource); 204*cdf0e10cSrcweir eventSource = NULL; 205*cdf0e10cSrcweir } 206*cdf0e10cSrcweir if (queue != NULL) { 207*cdf0e10cSrcweir (*queue)->stop(queue); 208*cdf0e10cSrcweir 209*cdf0e10cSrcweir //dispose of queue 210*cdf0e10cSrcweir (*queue)->dispose(queue); 211*cdf0e10cSrcweir 212*cdf0e10cSrcweir //release the queue we allocated 213*cdf0e10cSrcweir (*queue)->Release(queue); 214*cdf0e10cSrcweir 215*cdf0e10cSrcweir queue = NULL; 216*cdf0e10cSrcweir 217*cdf0e10cSrcweir sendNotification = YES; 218*cdf0e10cSrcweir } 219*cdf0e10cSrcweir 220*cdf0e10cSrcweir if (allCookies != nil) { 221*cdf0e10cSrcweir [allCookies autorelease]; 222*cdf0e10cSrcweir allCookies = nil; 223*cdf0e10cSrcweir } 224*cdf0e10cSrcweir 225*cdf0e10cSrcweir if (hidDeviceInterface != NULL) { 226*cdf0e10cSrcweir //close the device 227*cdf0e10cSrcweir (*hidDeviceInterface)->close(hidDeviceInterface); 228*cdf0e10cSrcweir 229*cdf0e10cSrcweir //release the interface 230*cdf0e10cSrcweir (*hidDeviceInterface)->Release(hidDeviceInterface); 231*cdf0e10cSrcweir 232*cdf0e10cSrcweir hidDeviceInterface = NULL; 233*cdf0e10cSrcweir } 234*cdf0e10cSrcweir 235*cdf0e10cSrcweir if ([self isOpenInExclusiveMode] && fixSecureEventInputBug) DisableSecureEventInput(); 236*cdf0e10cSrcweir 237*cdf0e10cSrcweir if ([self isOpenInExclusiveMode] && sendNotification) { 238*cdf0e10cSrcweir [[self class] sendFinishedNotifcationForAppIdentifier: nil]; 239*cdf0e10cSrcweir } 240*cdf0e10cSrcweir // be KVO friendly 241*cdf0e10cSrcweir [self willChangeValueForKey:@"listeningToRemote"]; 242*cdf0e10cSrcweir [self didChangeValueForKey:@"listeningToRemote"]; 243*cdf0e10cSrcweir} 244*cdf0e10cSrcweir 245*cdf0e10cSrcweir@end 246*cdf0e10cSrcweir 247*cdf0e10cSrcweir@implementation HIDRemoteControlDevice (PrivateMethods) 248*cdf0e10cSrcweir 249*cdf0e10cSrcweir- (IOHIDQueueInterface**) queue { 250*cdf0e10cSrcweir return queue; 251*cdf0e10cSrcweir} 252*cdf0e10cSrcweir 253*cdf0e10cSrcweir- (IOHIDDeviceInterface**) hidDeviceInterface { 254*cdf0e10cSrcweir return hidDeviceInterface; 255*cdf0e10cSrcweir} 256*cdf0e10cSrcweir 257*cdf0e10cSrcweir 258*cdf0e10cSrcweir- (NSDictionary*) cookieToButtonMapping { 259*cdf0e10cSrcweir return cookieToButtonMapping; 260*cdf0e10cSrcweir} 261*cdf0e10cSrcweir 262*cdf0e10cSrcweir- (NSString*) validCookieSubstring: (NSString*) cookieString { 263*cdf0e10cSrcweir if (cookieString == nil || [cookieString length] == 0) return nil; 264*cdf0e10cSrcweir NSEnumerator* keyEnum = [[self cookieToButtonMapping] keyEnumerator]; 265*cdf0e10cSrcweir NSString* key; 266*cdf0e10cSrcweir while( (key = [keyEnum nextObject]) ) { 267*cdf0e10cSrcweir NSRange range = [cookieString rangeOfString:key]; 268*cdf0e10cSrcweir if (range.location == 0) return key; 269*cdf0e10cSrcweir } 270*cdf0e10cSrcweir return nil; 271*cdf0e10cSrcweir} 272*cdf0e10cSrcweir 273*cdf0e10cSrcweir- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues { 274*cdf0e10cSrcweir /* 275*cdf0e10cSrcweir if (previousRemainingCookieString) { 276*cdf0e10cSrcweir cookieString = [previousRemainingCookieString stringByAppendingString: cookieString]; 277*cdf0e10cSrcweir NSLog(@"New cookie string is %@", cookieString); 278*cdf0e10cSrcweir [previousRemainingCookieString release], previousRemainingCookieString=nil; 279*cdf0e10cSrcweir }*/ 280*cdf0e10cSrcweir if (cookieString == nil || [cookieString length] == 0) return; 281*cdf0e10cSrcweir 282*cdf0e10cSrcweir NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString]; 283*cdf0e10cSrcweir if (buttonId != nil) { 284*cdf0e10cSrcweir [self sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)]; 285*cdf0e10cSrcweir } else { 286*cdf0e10cSrcweir // let's see if a number of events are stored in the cookie string. this does 287*cdf0e10cSrcweir // happen when the main thread is too busy to handle all incoming events in time. 288*cdf0e10cSrcweir NSString* subCookieString; 289*cdf0e10cSrcweir NSString* lastSubCookieString=nil; 290*cdf0e10cSrcweir while( (subCookieString = [self validCookieSubstring: cookieString]) ) { 291*cdf0e10cSrcweir cookieString = [cookieString substringFromIndex: [subCookieString length]]; 292*cdf0e10cSrcweir lastSubCookieString = subCookieString; 293*cdf0e10cSrcweir if (processesBacklog) [self handleEventWithCookieString: subCookieString sumOfValues:sumOfValues]; 294*cdf0e10cSrcweir } 295*cdf0e10cSrcweir if (processesBacklog == NO && lastSubCookieString != nil) { 296*cdf0e10cSrcweir // process the last event of the backlog and assume that the button is not pressed down any longer. 297*cdf0e10cSrcweir // The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be 298*cdf0e10cSrcweir // a button pressed down event while in reality the user has released it. 299*cdf0e10cSrcweir // NSLog(@"processing last event of backlog"); 300*cdf0e10cSrcweir [self handleEventWithCookieString: lastSubCookieString sumOfValues:0]; 301*cdf0e10cSrcweir } 302*cdf0e10cSrcweir if ([cookieString length] > 0) { 303*cdf0e10cSrcweir NSLog(@"Unknown button for cookiestring %@", cookieString); 304*cdf0e10cSrcweir } 305*cdf0e10cSrcweir } 306*cdf0e10cSrcweir} 307*cdf0e10cSrcweir 308*cdf0e10cSrcweir- (void) removeNotifcationObserver { 309*cdf0e10cSrcweir [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; 310*cdf0e10cSrcweir} 311*cdf0e10cSrcweir 312*cdf0e10cSrcweir- (void) remoteControlAvailable:(NSNotification *)notification { 313*cdf0e10cSrcweir [self removeNotifcationObserver]; 314*cdf0e10cSrcweir [self startListening: self]; 315*cdf0e10cSrcweir} 316*cdf0e10cSrcweir 317*cdf0e10cSrcweir@end 318*cdf0e10cSrcweir 319*cdf0e10cSrcweir/* Callback method for the device queue 320*cdf0e10cSrcweirWill be called for any event of any type (cookie) to which we subscribe 321*cdf0e10cSrcweir*/ 322*cdf0e10cSrcweirstatic void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) { 323*cdf0e10cSrcweir if (target < 0) { 324*cdf0e10cSrcweir NSLog(@"QueueCallbackFunction called with invalid target!"); 325*cdf0e10cSrcweir return; 326*cdf0e10cSrcweir } 327*cdf0e10cSrcweir NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 328*cdf0e10cSrcweir 329*cdf0e10cSrcweir HIDRemoteControlDevice* remote = (HIDRemoteControlDevice*)target; 330*cdf0e10cSrcweir IOHIDEventStruct event; 331*cdf0e10cSrcweir AbsoluteTime zeroTime = {0,0}; 332*cdf0e10cSrcweir NSMutableString* cookieString = [NSMutableString string]; 333*cdf0e10cSrcweir SInt32 sumOfValues = 0; 334*cdf0e10cSrcweir while (result == kIOReturnSuccess) 335*cdf0e10cSrcweir { 336*cdf0e10cSrcweir result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0); 337*cdf0e10cSrcweir if ( result != kIOReturnSuccess ) 338*cdf0e10cSrcweir continue; 339*cdf0e10cSrcweir 340*cdf0e10cSrcweir //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue); 341*cdf0e10cSrcweir 342*cdf0e10cSrcweir if (((int)event.elementCookie)!=5) { 343*cdf0e10cSrcweir sumOfValues+=event.value; 344*cdf0e10cSrcweir [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]]; 345*cdf0e10cSrcweir } 346*cdf0e10cSrcweir } 347*cdf0e10cSrcweir [remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues]; 348*cdf0e10cSrcweir 349*cdf0e10cSrcweir [pool release]; 350*cdf0e10cSrcweir} 351*cdf0e10cSrcweir 352*cdf0e10cSrcweir@implementation HIDRemoteControlDevice (IOKitMethods) 353*cdf0e10cSrcweir 354*cdf0e10cSrcweir- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice { 355*cdf0e10cSrcweir io_name_t className; 356*cdf0e10cSrcweir IOCFPlugInInterface** plugInInterface = NULL; 357*cdf0e10cSrcweir HRESULT plugInResult = S_OK; 358*cdf0e10cSrcweir SInt32 score = 0; 359*cdf0e10cSrcweir IOReturn ioReturnValue = kIOReturnSuccess; 360*cdf0e10cSrcweir 361*cdf0e10cSrcweir hidDeviceInterface = NULL; 362*cdf0e10cSrcweir 363*cdf0e10cSrcweir ioReturnValue = IOObjectGetClass(hidDevice, className); 364*cdf0e10cSrcweir 365*cdf0e10cSrcweir if (ioReturnValue != kIOReturnSuccess) { 366*cdf0e10cSrcweir NSLog(@"Error: Failed to get class name."); 367*cdf0e10cSrcweir return NULL; 368*cdf0e10cSrcweir } 369*cdf0e10cSrcweir 370*cdf0e10cSrcweir ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice, 371*cdf0e10cSrcweir kIOHIDDeviceUserClientTypeID, 372*cdf0e10cSrcweir kIOCFPlugInInterfaceID, 373*cdf0e10cSrcweir &plugInInterface, 374*cdf0e10cSrcweir &score); 375*cdf0e10cSrcweir if (ioReturnValue == kIOReturnSuccess) 376*cdf0e10cSrcweir { 377*cdf0e10cSrcweir //Call a method of the intermediate plug-in to create the device interface 378*cdf0e10cSrcweir plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface); 379*cdf0e10cSrcweir 380*cdf0e10cSrcweir if (plugInResult != S_OK) { 381*cdf0e10cSrcweir NSLog(@"Error: Couldn't create HID class device interface"); 382*cdf0e10cSrcweir } 383*cdf0e10cSrcweir // Release 384*cdf0e10cSrcweir if (plugInInterface) (*plugInInterface)->Release(plugInInterface); 385*cdf0e10cSrcweir } 386*cdf0e10cSrcweir return hidDeviceInterface; 387*cdf0e10cSrcweir} 388*cdf0e10cSrcweir 389*cdf0e10cSrcweir- (BOOL) initializeCookies { 390*cdf0e10cSrcweir IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface; 391*cdf0e10cSrcweir IOHIDElementCookie cookie; 392*cdf0e10cSrcweir long usage; 393*cdf0e10cSrcweir long usagePage; 394*cdf0e10cSrcweir id object; 395*cdf0e10cSrcweir NSArray* elements = nil; 396*cdf0e10cSrcweir NSDictionary* element; 397*cdf0e10cSrcweir IOReturn success; 398*cdf0e10cSrcweir 399*cdf0e10cSrcweir if (!handle || !(*handle)) return NO; 400*cdf0e10cSrcweir 401*cdf0e10cSrcweir // Copy all elements, since we're grabbing most of the elements 402*cdf0e10cSrcweir // for this device anyway, and thus, it's faster to iterate them 403*cdf0e10cSrcweir // ourselves. When grabbing only one or two elements, a matching 404*cdf0e10cSrcweir // dictionary should be passed in here instead of NULL. 405*cdf0e10cSrcweir success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements); 406*cdf0e10cSrcweir 407*cdf0e10cSrcweir if (success == kIOReturnSuccess) { 408*cdf0e10cSrcweir 409*cdf0e10cSrcweir [elements autorelease]; 410*cdf0e10cSrcweir /* 411*cdf0e10cSrcweir cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie)); 412*cdf0e10cSrcweir memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS); 413*cdf0e10cSrcweir */ 414*cdf0e10cSrcweir allCookies = [[NSMutableArray alloc] init]; 415*cdf0e10cSrcweir 416*cdf0e10cSrcweir NSEnumerator *elementsEnumerator = [elements objectEnumerator]; 417*cdf0e10cSrcweir 418*cdf0e10cSrcweir while ( (element = [elementsEnumerator nextObject]) ) { 419*cdf0e10cSrcweir //Get cookie 420*cdf0e10cSrcweir object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ]; 421*cdf0e10cSrcweir if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; 422*cdf0e10cSrcweir if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue; 423*cdf0e10cSrcweir cookie = (IOHIDElementCookie) [object longValue]; 424*cdf0e10cSrcweir 425*cdf0e10cSrcweir //Get usage 426*cdf0e10cSrcweir object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ]; 427*cdf0e10cSrcweir if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; 428*cdf0e10cSrcweir usage = [object longValue]; 429*cdf0e10cSrcweir 430*cdf0e10cSrcweir //Get usage page 431*cdf0e10cSrcweir object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ]; 432*cdf0e10cSrcweir if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; 433*cdf0e10cSrcweir usagePage = [object longValue]; 434*cdf0e10cSrcweir 435*cdf0e10cSrcweir [allCookies addObject: [NSNumber numberWithInt:(int)cookie]]; 436*cdf0e10cSrcweir } 437*cdf0e10cSrcweir } else { 438*cdf0e10cSrcweir return NO; 439*cdf0e10cSrcweir } 440*cdf0e10cSrcweir 441*cdf0e10cSrcweir return YES; 442*cdf0e10cSrcweir} 443*cdf0e10cSrcweir 444*cdf0e10cSrcweir- (BOOL) openDevice { 445*cdf0e10cSrcweir HRESULT result; 446*cdf0e10cSrcweir 447*cdf0e10cSrcweir IOHIDOptionsType openMode = kIOHIDOptionsTypeNone; 448*cdf0e10cSrcweir if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice; 449*cdf0e10cSrcweir IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode); 450*cdf0e10cSrcweir 451*cdf0e10cSrcweir if (ioReturnValue == KERN_SUCCESS) { 452*cdf0e10cSrcweir queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); 453*cdf0e10cSrcweir if (queue) { 454*cdf0e10cSrcweir result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost. 455*cdf0e10cSrcweir 456*cdf0e10cSrcweir IOHIDElementCookie cookie; 457*cdf0e10cSrcweir NSEnumerator *allCookiesEnumerator = [allCookies objectEnumerator]; 458*cdf0e10cSrcweir 459*cdf0e10cSrcweir while ( (cookie = (IOHIDElementCookie)[[allCookiesEnumerator nextObject] intValue]) ) { 460*cdf0e10cSrcweir (*queue)->addElement(queue, cookie, 0); 461*cdf0e10cSrcweir } 462*cdf0e10cSrcweir 463*cdf0e10cSrcweir // add callback for async events 464*cdf0e10cSrcweir ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource); 465*cdf0e10cSrcweir if (ioReturnValue == KERN_SUCCESS) { 466*cdf0e10cSrcweir ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL); 467*cdf0e10cSrcweir if (ioReturnValue == KERN_SUCCESS) { 468*cdf0e10cSrcweir CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); 469*cdf0e10cSrcweir 470*cdf0e10cSrcweir //start data delivery to queue 471*cdf0e10cSrcweir (*queue)->start(queue); 472*cdf0e10cSrcweir return YES; 473*cdf0e10cSrcweir } else { 474*cdf0e10cSrcweir NSLog(@"Error when setting event callback"); 475*cdf0e10cSrcweir } 476*cdf0e10cSrcweir } else { 477*cdf0e10cSrcweir NSLog(@"Error when creating async event source"); 478*cdf0e10cSrcweir } 479*cdf0e10cSrcweir } else { 480*cdf0e10cSrcweir NSLog(@"Error when opening device"); 481*cdf0e10cSrcweir } 482*cdf0e10cSrcweir } else if (ioReturnValue == kIOReturnExclusiveAccess) { 483*cdf0e10cSrcweir // the device is used exclusive by another application 484*cdf0e10cSrcweir 485*cdf0e10cSrcweir // 1. we register for the FINISHED_USING_REMOTE_CONTROL_NOTIFICATION notification 486*cdf0e10cSrcweir [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(remoteControlAvailable:) name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; 487*cdf0e10cSrcweir 488*cdf0e10cSrcweir // 2. send a distributed notification that we wanted to use the remote control 489*cdf0e10cSrcweir [[self class] sendRequestForRemoteControlNotification]; 490*cdf0e10cSrcweir } 491*cdf0e10cSrcweir return NO; 492*cdf0e10cSrcweir} 493*cdf0e10cSrcweir 494*cdf0e10cSrcweir+ (io_object_t) findRemoteDevice { 495*cdf0e10cSrcweir CFMutableDictionaryRef hidMatchDictionary = NULL; 496*cdf0e10cSrcweir IOReturn ioReturnValue = kIOReturnSuccess; 497*cdf0e10cSrcweir io_iterator_t hidObjectIterator = 0; 498*cdf0e10cSrcweir io_object_t hidDevice = 0; 499*cdf0e10cSrcweir 500*cdf0e10cSrcweir // Set up a matching dictionary to search the I/O Registry by class 501*cdf0e10cSrcweir // name for all HID class devices 502*cdf0e10cSrcweir hidMatchDictionary = IOServiceMatching([self remoteControlDeviceName]); 503*cdf0e10cSrcweir 504*cdf0e10cSrcweir // Now search I/O Registry for matching devices. 505*cdf0e10cSrcweir ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); 506*cdf0e10cSrcweir 507*cdf0e10cSrcweir if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { 508*cdf0e10cSrcweir hidDevice = IOIteratorNext(hidObjectIterator); 509*cdf0e10cSrcweir } 510*cdf0e10cSrcweir 511*cdf0e10cSrcweir // release the iterator 512*cdf0e10cSrcweir IOObjectRelease(hidObjectIterator); 513*cdf0e10cSrcweir 514*cdf0e10cSrcweir return hidDevice; 515*cdf0e10cSrcweir} 516*cdf0e10cSrcweir 517*cdf0e10cSrcweir@end 518*cdf0e10cSrcweir 519