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