1
2#import "IrDAExtra.h"
3#import <sys/stat.h>
4#import <SystemConfiguration/SystemConfiguration.h>
5#import <CoreFoundation/CFLogUtilities.h> // CFLogTest
6
7//#include <HIServices/CoreDockDocklingServer.h>
8#include <ApplicationServices/ApplicationServicesPriv.h>
9#include <IOKit/IOMessage.h>
10
11enum{
12	kIrDA1_Idle = 0,
13	kIrDA2_Discovering,
14	kIrDA3_Connected,
15	kIrDA4_BrokenBeam,
16	kIrDA5_Invalid,
17	kIrDA6_Off,
18	kNumPictures
19};
20
21@implementation IrDAExtra
22// driverCallback
23static void driverCallback(void *refcon, io_service_t service, uint32_t messageType, void* messageArgument)
24{
25	switch (messageType){
26		case kIOMessageServiceIsTerminated:
27			NSLog(@"driverCallback: messageType = kIOMessageServiceIsTerminated");
28			{
29				IrDAExtra	*temp = refcon;
30				
31				[temp stopNotification];
32				CoreMenuExtraRemoveMenuExtra (0, temp->mBundleID);
33			}
34			break;
35		case kIrDACallBack_Status:
36			{
37			    // irda state is passed back to us in high-byte of messageArgument (ppc), or low byte if intel
38			    // http://lists.apple.com/archives/darwin-development/2003/Oct/msg00063.html
39			    // could probably clean up below ..
40#if defined(__ppc__)
41			    UInt8 status = ((uintptr_t)messageArgument) >> 24;
42#endif
43#if (defined(__i386__) || defined(__x86_64__))
44			    UInt8 status = ((uintptr_t)messageArgument) & 0xff;
45#endif    
46			    IrDAExtra	*temp = refcon;
47	
48			    [temp updateState:status];	
49			}
50			break;
51		case kIrDACallBack_Unplug:
52			NSLog(@"driverCallback: messageType = kIrDACallBack_Unplug");
53			{
54				IrDAExtra	*temp = refcon;
55				
56				[temp stopNotification];
57				CoreMenuExtraRemoveMenuExtra (0, temp->mBundleID);
58			}
59			break;
60		default:
61			NSLog(@"driverCallback: messageType = %d", messageType);
62			break;
63	};
64	
65}
66
67- (void) setIrDAImage
68{
69	[self setImage:[mImages objectAtIndex:mCurrentImage]];
70}
71
72- (void)updateState:(UInt8)newState
73{
74	BOOL			makeSound	= NO;
75	
76	mIrDAState = newState;
77	switch (mIrDAState){
78		case kIrDAStatusIdle:				mCurrentImage = kIrDA1_Idle;												break;
79		case kIrDAStatusDiscoverActive:		mCurrentImage = kIrDA2_Discovering;											break;
80		case kIrDAStatusConnected:			mCurrentImage = kIrDA3_Connected;		makeSound = YES;					break;
81		case kIrDAStatusBrokenConnection:	mCurrentImage = kIrDA4_BrokenBeam;		makeSound = YES;					break;
82		case kIrDAStatusOff:				mCurrentImage = kIrDA6_Off;													break;
83		case kIrDAStatusInvalid:			mCurrentImage = kIrDA5_Invalid;												break;
84		default:							mCurrentImage = kIrDA5_Invalid;			mIrDAState = kIrDAStatusInvalid;	break;
85	};
86	[self setIrDAImage];
87	if (makeSound && mSoundState){
88		NSBeep();
89	}
90}
91
92- (void) setmName:(UInt8 *)name{
93	[mName release];
94	mName = [NSString stringWithUTF8String:(const char *)name];
95	[mName retain];
96}
97
98/* close  the driver */
99- (void) closeIrDA
100{
101	IOServiceClose(mConObj);
102}
103
104/* open up the driver and leave it open so we can talk to it */
105- (void) openIrDA
106{
107	IOServiceOpen(mDriverObject, mach_task_self(), 123, &mConObj);
108}
109
110- (void) PollState
111{
112    kern_return_t			kr;
113    IrDAStatus				stats;
114    size_t	outputsize = sizeof(stats);
115
116	[self openIrDA];
117	kr = doCommand(mConObj, kIrDAUserCmd_GetStatus, nil, 0, &stats, &outputsize);
118	if (kr == kIOReturnSuccess) {
119		mIrDAState = stats.connectionState;
120	}
121	switch (mIrDAState){
122		case kIrDAStatusIdle:				mCurrentImage = kIrDA1_Idle;												break;
123		case kIrDAStatusDiscoverActive:		mCurrentImage = kIrDA2_Discovering;											break;
124		case kIrDAStatusConnected:			mCurrentImage = kIrDA3_Connected;		[self setmName:stats.nickName];		break;
125		case kIrDAStatusBrokenConnection:	mCurrentImage = kIrDA4_BrokenBeam;											break;
126		case kIrDAStatusOff:				mCurrentImage = kIrDA6_Off;													break;
127		case kIrDAStatusInvalid:			mCurrentImage = kIrDA5_Invalid;												break;
128		default:							mCurrentImage = kIrDA5_Invalid;			mIrDAState = kIrDAStatusInvalid;	break;
129	};
130	[self closeIrDA];
131	[self setIrDAImage];
132}
133
134- (void)menuActionSoundOn:(id)sender
135{
136	CFPreferencesSetAppValue(CFSTR("UseSoundForIrDA"), CFSTR("YES"), mBundleID);
137	CFPreferencesAppSynchronize(mBundleID);
138	mSoundState = YES;
139}
140- (void)menuActionSoundOff:(id)sender
141{
142	CFPreferencesSetAppValue(CFSTR("UseSoundForIrDA"), CFSTR("NO"), mBundleID);
143	CFPreferencesAppSynchronize(mBundleID);
144	mSoundState = NO;
145}
146- (void)menuActionNetworkPrefs:(id)sender
147{
148	[[NSWorkspace sharedWorkspace] openFile:@"/System/Library/PreferencePanes/Network.prefPane"];
149}
150- (void)menuActionPowerOn:(id)sender
151{
152    kern_return_t	kr;
153    size_t outputsize = 0;
154
155	[self openIrDA];
156	kr = doCommand(mConObj, kIrDAUserCmd_Enable, nil, 0, nil, &outputsize);
157	if (kr == kIOReturnSuccess) {
158		CFPreferencesSetAppValue(CFSTR("UseIrDAHardware"), CFSTR("YES"), mBundleID);
159		CFPreferencesAppSynchronize(mBundleID);
160	}
161	[self closeIrDA];
162}
163- (void)menuActionPowerOff:(id)sender
164{
165    kern_return_t			kr;
166    size_t outputsize = 0;
167
168	[self openIrDA];
169	kr = doCommand(mConObj, kIrDAUserCmd_Disable, nil, 0, nil, &outputsize);
170	if (kr == kIOReturnSuccess) {
171		CFPreferencesSetAppValue(CFSTR("UseIrDAHardware"), CFSTR("NO"), mBundleID);
172		CFPreferencesAppSynchronize(mBundleID);
173	}
174	[self closeIrDA];
175}
176- (void)CheckPrefs
177{
178	Boolean		temp;
179	Boolean		valid;
180	/* Check Sound state */
181	mSoundState = CFPreferencesGetAppBooleanValue(CFSTR("UseSoundForIrDA"), mBundleID, &valid);
182	/* Check Power state */
183	temp = CFPreferencesGetAppBooleanValue(CFSTR("UseIrDAHardware"), mBundleID, &valid);
184	if (temp){
185		[self menuActionPowerOn:self];
186	}
187}
188
189/* This routine will need to generate the menu based on the current state of the system. */
190- (NSMenu*) menu
191{
192    NSMenu		*menu;
193    NSMenuItem 	*item;
194    
195	[self PollState];					// Update the state just in case
196	
197	menu = [[[NSMenu alloc] initWithTitle:@"MenuTitle"] autorelease];
198	
199	/* Only one Menu Item if we are invalid */
200	if (mIrDAState == kIrDAStatusInvalid){				
201		item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Invalid" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
202		[item setTarget:self];
203		[menu addItem: item];
204		return menu;
205	};
206
207	/* Only one Menu Item if we are turned off */
208	if (mIrDAState == kIrDAStatusOff){				
209		item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"Turn IrDA On" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
210		[item setAction:@selector(menuActionPowerOn:)];
211		[item setTarget:self];
212		[menu addItem: item];
213		mIrDAState = kIrDAStatusInvalid;
214		return menu;
215	};
216
217	/* First Menu Item, "Status" Should be disabled*/
218	switch (mIrDAState){
219		case kIrDAStatusIdle:
220			item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Idle" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
221			break;
222		case kIrDAStatusDiscoverActive:
223			item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Discovering" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
224			break;
225		case kIrDAStatusConnected:
226			{
227				NSString *part1 = [mBundle localizedStringForKey: @"IrDA: Connected (" value: @"" table: @"menu"];
228				NSString *part2 = [part1 stringByAppendingString:mName];
229				NSString *part3 = [part2 stringByAppendingString:[mBundle localizedStringForKey: @")" value: @"" table: @"menu"]];
230
231				item = [[[NSMenuItem alloc] initWithTitle:part3 action: NULL keyEquivalent:@""] autorelease];
232			}
233			break;
234		case kIrDAStatusBrokenConnection:
235			item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Broken Beam" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
236			break;
237		default:
238			item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Invalid" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
239			break;
240	};
241	[menu addItem: item];
242
243	/* Second Menu Item, Power On/Off */
244	item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"Turn IrDA Off" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
245	[item setAction:@selector(menuActionPowerOff:)];
246	[item setTarget:self];
247	[menu addItem: item];
248
249	/* Fifth Menu Item, Seperator */
250	[menu addItem:[NSMenuItem separatorItem]];
251
252	/* Sixth Menu Item, Sound On/Off */
253	item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"Use Sound Effects" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
254	if (mSoundState){
255		[item setState:NSOnState];
256		[item setAction:@selector(menuActionSoundOff:)];
257	}
258	else{
259		[item setState:NSOffState];
260		[item setAction:@selector(menuActionSoundOn:)];
261	}
262	[item setTarget:self];
263	[menu addItem: item];
264
265	/* Seventh Menu Item, Seperator */
266	[menu addItem:[NSMenuItem separatorItem]];
267
268	/* Eigth Menu Item, Open Internet Connect / Network Prefs*/
269	item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"Open Network Preferences..." value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
270	[item setAction:@selector(menuActionNetworkPrefs:)];
271	[item setTarget:self];
272	[menu addItem: item];
273	
274	mIrDAState = kIrDAStatusInvalid;
275    return menu;
276}
277- (void)InitImages
278{
279	NSImage *image;
280	
281	mImages = [[NSMutableArray alloc] initWithCapacity: kNumPictures];
282	
283	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA1IdleCropped.pdf" ofType:nil]] autorelease];
284	[image setTemplate:YES];
285	[mImages addObject:image];
286		
287	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA2DiscoveringCropped.pdf" ofType:nil]] autorelease];
288	[image setTemplate:YES];
289	[mImages addObject:image];
290
291	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA3ConnectedCropped.pdf" ofType:nil]] autorelease];
292	[image setTemplate:YES];
293	[mImages addObject:image];
294		
295	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA4BrokenBeamCropped.pdf" ofType:nil]] autorelease];
296	[image setTemplate:YES];
297	[mImages addObject:image];
298		
299	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA5InvalidCropped.pdf" ofType:nil]] autorelease];
300	[image setTemplate:YES];
301	[mImages addObject:image];
302		
303	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA6OffCropped.pdf" ofType:nil]] autorelease];
304	[image setTemplate:YES];
305	[mImages addObject:image];
306		
307	mCurrentImage = kIrDA5_Invalid;
308	[self setIrDAImage];
309}
310
311- (BOOL)convertedForNewUI 
312{ 
313	return YES; 
314}
315
316- (BOOL) searchForDriver
317{
318    mach_port_t		masterPort;
319    kern_return_t	kr;
320	
321    // Get master device port
322    //
323    kr = IOMasterPort(bootstrap_port, &masterPort);
324    if (kr == KERN_SUCCESS) {
325		mDriverObject = getInterfaceWithName(masterPort, "AppleIrDA");
326		if (mDriverObject) {
327			return YES;
328		}
329    }
330	return NO;
331}
332
333/* This has been migrated from NSPrefs to CFPreferences */
334- (void) DefaultPrefs{
335    CFTypeRef	ref;
336    
337    mBundleID = (CFStringRef)[mBundle bundleIdentifier];
338    CFRetain(mBundleID);
339
340    /* UseIrDAHardware */
341    ref = CFPreferencesCopyAppValue(CFSTR("UseIrDAHardware"), mBundleID);
342    if (ref == nil){
343	CFPreferencesSetAppValue(CFSTR("UseIrDAHardware"), CFSTR("NO"), mBundleID);
344	CFPreferencesAppSynchronize(mBundleID);
345    }
346    else{
347	CFRelease(ref);
348	ref = nil;
349    }
350    /* UseSoundForIrDA */
351    ref = CFPreferencesCopyAppValue(CFSTR("UseSoundForIrDA"), mBundleID);
352    if (ref == nil){
353	CFPreferencesSetAppValue(CFSTR("UseSoundForIrDA"), CFSTR("YES"), mBundleID);
354	CFPreferencesAppSynchronize(mBundleID);
355    }
356    else{
357	CFRelease(ref);
358	ref = nil;
359    }
360}
361
362- (void) startNotification
363{
364    kern_return_t	kr;
365    
366    mNotifyPort = IONotificationPortCreate(kIOMasterPortDefault);
367    
368    if (mNotifyPort) {
369	mNotification = IO_OBJECT_NULL;
370	
371	kr = IOServiceAddInterestNotification(mNotifyPort, mDriverObject, kIOGeneralInterest, &driverCallback, (void *) self, &mNotification);
372	if (kr== KERN_SUCCESS){
373	    CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(mNotifyPort), kCFRunLoopDefaultMode);
374	}
375    }
376}
377
378- (void) stopNotification
379{
380    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(mNotifyPort), kCFRunLoopDefaultMode);
381    if (mNotification) {
382	IOObjectRelease(mNotification);
383	mNotification = IO_OBJECT_NULL;
384    }
385    if (mNotifyPort) {
386	IONotificationPortDestroy(mNotifyPort);
387	mNotifyPort = NULL;
388    }
389}
390
391/* Get everything ready to go */
392- (id)initWithBundle:(NSBundle*)bundle
393{
394    self = [super initWithBundle:bundle];
395    
396    //NSLog(@"initWithBundle:");
397    if (self != nil){
398		if ([self searchForDriver]){
399			mBundle = bundle;
400			[mBundle retain];																		// Don't throw the bundle away
401			mName = @"";
402			[mName retain];																			// Keep me 
403			[self DefaultPrefs];																	// set default Prefs
404			[self InitImages];																		// Load all of the images
405			[self CheckPrefs];																		// See what current prefs are
406			[self startNotification];																// Let the driver know we care
407			[self PollState];
408		}
409		else{
410			[super dealloc];	// I am not sure I need this
411			return (nil);
412		}
413    }
414    return self;
415}
416
417/* Get rid of everything allocated in init */
418- (void) dealloc
419{
420	[self stopNotification];
421	[mBundle release];
422	[mImages release];
423	[mName release]; 
424	CFRelease(mBundleID);
425	IOObjectRelease(mDriverObject);
426	[super dealloc];
427}
428
429- (void) _testSleep:(NSTimeInterval) sleepTime
430{
431    if (sleepTime>0)
432	[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:sleepTime]];
433}
434
435#define MaxWaitTime  15	// max time in seconds for a connection, 3 is typical.  Off takes about 1.
436
437- (void) _testrun:(int)iteration
438{
439    int i;
440    static int worked, failed;
441    CFLogTest(0, CFSTR( "Iteration:%d %@ running IrDA on/off self test" ), iteration, [self className]);
442    [self menuActionPowerOn:self];
443    for (i = 0 ; i < MaxWaitTime; i++) {
444	if (mIrDAState == kIrDAStatusConnected) {
445	    worked++;
446	    [self PollState];	    // to get nicname of peer
447	    CFLogTest(0, CFSTR( "Iteration:%d %@ connected after %d seconds with '%@'.  Worked %d, failed %d, %4.1f%%." ),
448		      iteration, [self className], i, mName, worked, failed, 100.*worked / (double)(worked + failed));
449	    break;
450	}
451	[self _testSleep:1];
452    }
453    if (mIrDAState != kIrDAStatusConnected) {
454	failed++;
455	CFLogTest(0, CFSTR( "Iteration:%d %@ did not connect.  Worked %d, failed %d, %4.1f%%." ),
456		  iteration, [self className], worked, failed, 100.*worked / (double)(worked + failed));
457    }
458
459    [self menuActionPowerOff:self];
460    for (i = 0 ; i < MaxWaitTime ; i++) {
461	if (mIrDAState == kIrDAStatusOff) {
462	    break;
463	}
464	[self _testSleep:1];
465    }
466    [self _testSleep:2];	// some settle time before reconnect
467}
468
469// Only one test implemented, so inTestToRun is ignored.  And inDuration is also ignored, as the SystemUIServer
470// will loop calling all of it's menu extras until the total duration time has elapsed.  We need to return after
471// a single test to give equal time to the other menu extras.
472
473- (void) runSelfTest:(unsigned int)inTestToRun duration:(NSTimeInterval)inDuration
474{
475    static int iteration = 0;
476    iteration++;
477    
478    [self _testrun:iteration];	    // run an on/off sequence
479    
480    // given we can't tell which is the last iteration, we're not
481    // bothering to restore state after the last test run.
482}
483
484
485
486@end
487