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