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