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