IrDAExtra.m   [plain text]



#import "IrDAExtra.h"
#import <sys/stat.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import <CoreFoundation/CFLogUtilities.h> // CFLogTest

//#include <HIServices/CoreDockDocklingServer.h>
#include <ApplicationServices/ApplicationServicesPriv.h>
#include <IOKit/IOMessage.h>

enum{
	kIrDA1_Idle = 0,
	kIrDA2_Discovering,
	kIrDA3_Connected,
	kIrDA4_BrokenBeam,
	kIrDA5_Invalid,
	kIrDA6_Off,
	kNumPictures
};

@implementation IrDAExtra
// driverCallback
static void driverCallback(void *refcon, io_service_t service, uint32_t messageType, void* messageArgument)
{
	switch (messageType){
		case kIOMessageServiceIsTerminated:
			NSLog(@"driverCallback: messageType = kIOMessageServiceIsTerminated");
			{
				IrDAExtra	*temp = refcon;
				
				[temp stopNotification];
				CoreMenuExtraRemoveMenuExtra (0, temp->mBundleID);
			}
			break;
		case kIrDACallBack_Status:
			{
			    // irda state is passed back to us in high-byte of messageArgument (ppc), or low byte if intel
			    // http://lists.apple.com/archives/darwin-development/2003/Oct/msg00063.html
			    // could probably clean up below ..
#if defined(__ppc__)
			    UInt8 status = ((uintptr_t)messageArgument) >> 24;
#endif
#if (defined(__i386__) || defined(__x86_64__))
			    UInt8 status = ((uintptr_t)messageArgument) & 0xff;
#endif    
			    IrDAExtra	*temp = refcon;
	
			    [temp updateState:status];	
			}
			break;
		case kIrDACallBack_Unplug:
			NSLog(@"driverCallback: messageType = kIrDACallBack_Unplug");
			{
				IrDAExtra	*temp = refcon;
				
				[temp stopNotification];
				CoreMenuExtraRemoveMenuExtra (0, temp->mBundleID);
			}
			break;
		default:
			NSLog(@"driverCallback: messageType = %d", messageType);
			break;
	};
	
}

- (void) setIrDAImage
{
	[self setImage:[mImages objectAtIndex:mCurrentImage]];
}

- (void)updateState:(UInt8)newState
{
	BOOL			makeSound	= NO;
	
	mIrDAState = newState;
	switch (mIrDAState){
		case kIrDAStatusIdle:				mCurrentImage = kIrDA1_Idle;												break;
		case kIrDAStatusDiscoverActive:		mCurrentImage = kIrDA2_Discovering;											break;
		case kIrDAStatusConnected:			mCurrentImage = kIrDA3_Connected;		makeSound = YES;					break;
		case kIrDAStatusBrokenConnection:	mCurrentImage = kIrDA4_BrokenBeam;		makeSound = YES;					break;
		case kIrDAStatusOff:				mCurrentImage = kIrDA6_Off;													break;
		case kIrDAStatusInvalid:			mCurrentImage = kIrDA5_Invalid;												break;
		default:							mCurrentImage = kIrDA5_Invalid;			mIrDAState = kIrDAStatusInvalid;	break;
	};
	[self setIrDAImage];
	if (makeSound && mSoundState){
		NSBeep();
	}
}

- (void) setmName:(UInt8 *)name{
	[mName release];
	mName = [NSString stringWithUTF8String:(const char *)name];
	[mName retain];
}

/* close  the driver */
- (void) closeIrDA
{
	IOServiceClose(mConObj);
}

/* open up the driver and leave it open so we can talk to it */
- (void) openIrDA
{
	IOServiceOpen(mDriverObject, mach_task_self(), 123, &mConObj);
}

- (void) PollState
{
    kern_return_t			kr;
    IrDAStatus				stats;
    size_t	outputsize = sizeof(stats);

	[self openIrDA];
	kr = doCommand(mConObj, kIrDAUserCmd_GetStatus, nil, 0, &stats, &outputsize);
	if (kr == kIOReturnSuccess) {
		mIrDAState = stats.connectionState;
	}
	switch (mIrDAState){
		case kIrDAStatusIdle:				mCurrentImage = kIrDA1_Idle;												break;
		case kIrDAStatusDiscoverActive:		mCurrentImage = kIrDA2_Discovering;											break;
		case kIrDAStatusConnected:			mCurrentImage = kIrDA3_Connected;		[self setmName:stats.nickName];		break;
		case kIrDAStatusBrokenConnection:	mCurrentImage = kIrDA4_BrokenBeam;											break;
		case kIrDAStatusOff:				mCurrentImage = kIrDA6_Off;													break;
		case kIrDAStatusInvalid:			mCurrentImage = kIrDA5_Invalid;												break;
		default:							mCurrentImage = kIrDA5_Invalid;			mIrDAState = kIrDAStatusInvalid;	break;
	};
	[self closeIrDA];
	[self setIrDAImage];
}

- (void)menuActionSoundOn:(id)sender
{
	CFPreferencesSetAppValue(CFSTR("UseSoundForIrDA"), CFSTR("YES"), mBundleID);
	CFPreferencesAppSynchronize(mBundleID);
	mSoundState = YES;
}
- (void)menuActionSoundOff:(id)sender
{
	CFPreferencesSetAppValue(CFSTR("UseSoundForIrDA"), CFSTR("NO"), mBundleID);
	CFPreferencesAppSynchronize(mBundleID);
	mSoundState = NO;
}
- (void)menuActionNetworkPrefs:(id)sender
{
	[[NSWorkspace sharedWorkspace] openFile:@"/System/Library/PreferencePanes/Network.prefPane"];
}
- (void)menuActionPowerOn:(id)sender
{
    kern_return_t	kr;
    size_t outputsize = 0;

	[self openIrDA];
	kr = doCommand(mConObj, kIrDAUserCmd_Enable, nil, 0, nil, &outputsize);
	if (kr == kIOReturnSuccess) {
		CFPreferencesSetAppValue(CFSTR("UseIrDAHardware"), CFSTR("YES"), mBundleID);
		CFPreferencesAppSynchronize(mBundleID);
	}
	[self closeIrDA];
}
- (void)menuActionPowerOff:(id)sender
{
    kern_return_t			kr;
    size_t outputsize = 0;

	[self openIrDA];
	kr = doCommand(mConObj, kIrDAUserCmd_Disable, nil, 0, nil, &outputsize);
	if (kr == kIOReturnSuccess) {
		CFPreferencesSetAppValue(CFSTR("UseIrDAHardware"), CFSTR("NO"), mBundleID);
		CFPreferencesAppSynchronize(mBundleID);
	}
	[self closeIrDA];
}
- (void)CheckPrefs
{
	Boolean		temp;
	Boolean		valid;
	/* Check Sound state */
	mSoundState = CFPreferencesGetAppBooleanValue(CFSTR("UseSoundForIrDA"), mBundleID, &valid);
	/* Check Power state */
	temp = CFPreferencesGetAppBooleanValue(CFSTR("UseIrDAHardware"), mBundleID, &valid);
	if (temp){
		[self menuActionPowerOn:self];
	}
}

/* This routine will need to generate the menu based on the current state of the system. */
- (NSMenu*) menu
{
    NSMenu		*menu;
    NSMenuItem 	*item;
    
	[self PollState];					// Update the state just in case
	
	menu = [[[NSMenu alloc] initWithTitle:@"MenuTitle"] autorelease];
	
	/* Only one Menu Item if we are invalid */
	if (mIrDAState == kIrDAStatusInvalid){				
		item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Invalid" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
		[item setTarget:self];
		[menu addItem: item];
		return menu;
	};

	/* Only one Menu Item if we are turned off */
	if (mIrDAState == kIrDAStatusOff){				
		item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"Turn IrDA On" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
		[item setAction:@selector(menuActionPowerOn:)];
		[item setTarget:self];
		[menu addItem: item];
		mIrDAState = kIrDAStatusInvalid;
		return menu;
	};

	/* First Menu Item, "Status" Should be disabled*/
	switch (mIrDAState){
		case kIrDAStatusIdle:
			item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Idle" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
			break;
		case kIrDAStatusDiscoverActive:
			item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Discovering" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
			break;
		case kIrDAStatusConnected:
			{
				NSString *part1 = [mBundle localizedStringForKey: @"IrDA: Connected (" value: @"" table: @"menu"];
				NSString *part2 = [part1 stringByAppendingString:mName];
				NSString *part3 = [part2 stringByAppendingString:[mBundle localizedStringForKey: @")" value: @"" table: @"menu"]];

				item = [[[NSMenuItem alloc] initWithTitle:part3 action: NULL keyEquivalent:@""] autorelease];
			}
			break;
		case kIrDAStatusBrokenConnection:
			item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Broken Beam" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
			break;
		default:
			item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"IrDA: Invalid" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
			break;
	};
	[menu addItem: item];

	/* Second Menu Item, Power On/Off */
	item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"Turn IrDA Off" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
	[item setAction:@selector(menuActionPowerOff:)];
	[item setTarget:self];
	[menu addItem: item];

	/* Fifth Menu Item, Seperator */
	[menu addItem:[NSMenuItem separatorItem]];

	/* Sixth Menu Item, Sound On/Off */
	item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"Use Sound Effects" value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
	if (mSoundState){
		[item setState:NSOnState];
		[item setAction:@selector(menuActionSoundOff:)];
	}
	else{
		[item setState:NSOffState];
		[item setAction:@selector(menuActionSoundOn:)];
	}
	[item setTarget:self];
	[menu addItem: item];

	/* Seventh Menu Item, Seperator */
	[menu addItem:[NSMenuItem separatorItem]];

	/* Eigth Menu Item, Open Internet Connect / Network Prefs*/
	item = [[[NSMenuItem alloc] initWithTitle:[mBundle localizedStringForKey: @"Open Network Preferences..." value: @"" table: @"menu"] action: NULL keyEquivalent:@""] autorelease];
	[item setAction:@selector(menuActionNetworkPrefs:)];
	[item setTarget:self];
	[menu addItem: item];
	
	mIrDAState = kIrDAStatusInvalid;
    return menu;
}
- (void)InitImages
{
	NSImage *image;
	
	mImages = [[NSMutableArray alloc] initWithCapacity: kNumPictures];
	
	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA1IdleCropped.pdf" ofType:nil]] autorelease];
	[image setTemplate:YES];
	[mImages addObject:image];
		
	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA2DiscoveringCropped.pdf" ofType:nil]] autorelease];
	[image setTemplate:YES];
	[mImages addObject:image];

	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA3ConnectedCropped.pdf" ofType:nil]] autorelease];
	[image setTemplate:YES];
	[mImages addObject:image];
		
	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA4BrokenBeamCropped.pdf" ofType:nil]] autorelease];
	[image setTemplate:YES];
	[mImages addObject:image];
		
	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA5InvalidCropped.pdf" ofType:nil]] autorelease];
	[image setTemplate:YES];
	[mImages addObject:image];
		
	image = [[[NSImage alloc] initWithContentsOfFile:[mBundle pathForResource:@"IRDA6OffCropped.pdf" ofType:nil]] autorelease];
	[image setTemplate:YES];
	[mImages addObject:image];
		
	mCurrentImage = kIrDA5_Invalid;
	[self setIrDAImage];
}

- (BOOL)convertedForNewUI 
{ 
	return YES; 
}

- (BOOL) searchForDriver
{
    mach_port_t		masterPort;
    kern_return_t	kr;
	
    // Get master device port
    //
    kr = IOMasterPort(bootstrap_port, &masterPort);
    if (kr == KERN_SUCCESS) {
		mDriverObject = getInterfaceWithName(masterPort, "AppleIrDA");
		if (mDriverObject) {
			return YES;
		}
    }
	return NO;
}

/* This has been migrated from NSPrefs to CFPreferences */
- (void) DefaultPrefs{
    CFTypeRef	ref;
    
    mBundleID = (CFStringRef)[mBundle bundleIdentifier];
    CFRetain(mBundleID);

    /* UseIrDAHardware */
    ref = CFPreferencesCopyAppValue(CFSTR("UseIrDAHardware"), mBundleID);
    if (ref == nil){
	CFPreferencesSetAppValue(CFSTR("UseIrDAHardware"), CFSTR("NO"), mBundleID);
	CFPreferencesAppSynchronize(mBundleID);
    }
    else{
	CFRelease(ref);
	ref = nil;
    }
    /* UseSoundForIrDA */
    ref = CFPreferencesCopyAppValue(CFSTR("UseSoundForIrDA"), mBundleID);
    if (ref == nil){
	CFPreferencesSetAppValue(CFSTR("UseSoundForIrDA"), CFSTR("YES"), mBundleID);
	CFPreferencesAppSynchronize(mBundleID);
    }
    else{
	CFRelease(ref);
	ref = nil;
    }
}

- (void) startNotification
{
    kern_return_t	kr;
    
    mNotifyPort = IONotificationPortCreate(kIOMasterPortDefault);
    
    if (mNotifyPort) {
	mNotification = IO_OBJECT_NULL;
	
	kr = IOServiceAddInterestNotification(mNotifyPort, mDriverObject, kIOGeneralInterest, &driverCallback, (void *) self, &mNotification);
	if (kr== KERN_SUCCESS){
	    CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(mNotifyPort), kCFRunLoopDefaultMode);
	}
    }
}

- (void) stopNotification
{
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(mNotifyPort), kCFRunLoopDefaultMode);
    if (mNotification) {
	IOObjectRelease(mNotification);
	mNotification = IO_OBJECT_NULL;
    }
    if (mNotifyPort) {
	IONotificationPortDestroy(mNotifyPort);
	mNotifyPort = NULL;
    }
}

/* Get everything ready to go */
- (id)initWithBundle:(NSBundle*)bundle
{
    self = [super initWithBundle:bundle];
    
    //NSLog(@"initWithBundle:");
    if (self != nil){
		if ([self searchForDriver]){
			mBundle = bundle;
			[mBundle retain];																		// Don't throw the bundle away
			mName = @"";
			[mName retain];																			// Keep me 
			[self DefaultPrefs];																	// set default Prefs
			[self InitImages];																		// Load all of the images
			[self CheckPrefs];																		// See what current prefs are
			[self startNotification];																// Let the driver know we care
			[self PollState];
		}
		else{
			[super dealloc];	// I am not sure I need this
			return (nil);
		}
    }
    return self;
}

/* Get rid of everything allocated in init */
- (void) dealloc
{
	[self stopNotification];
	[mBundle release];
	[mImages release];
	[mName release]; 
	CFRelease(mBundleID);
	IOObjectRelease(mDriverObject);
	[super dealloc];
}

- (void) _testSleep:(NSTimeInterval) sleepTime
{
    if (sleepTime>0)
	[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:sleepTime]];
}

#define MaxWaitTime  15	// max time in seconds for a connection, 3 is typical.  Off takes about 1.

- (void) _testrun:(int)iteration
{
    int i;
    static int worked, failed;
    CFLogTest(0, CFSTR( "Iteration:%d %@ running IrDA on/off self test" ), iteration, [self className]);
    [self menuActionPowerOn:self];
    for (i = 0 ; i < MaxWaitTime; i++) {
	if (mIrDAState == kIrDAStatusConnected) {
	    worked++;
	    [self PollState];	    // to get nicname of peer
	    CFLogTest(0, CFSTR( "Iteration:%d %@ connected after %d seconds with '%@'.  Worked %d, failed %d, %4.1f%%." ),
		      iteration, [self className], i, mName, worked, failed, 100.*worked / (double)(worked + failed));
	    break;
	}
	[self _testSleep:1];
    }
    if (mIrDAState != kIrDAStatusConnected) {
	failed++;
	CFLogTest(0, CFSTR( "Iteration:%d %@ did not connect.  Worked %d, failed %d, %4.1f%%." ),
		  iteration, [self className], worked, failed, 100.*worked / (double)(worked + failed));
    }

    [self menuActionPowerOff:self];
    for (i = 0 ; i < MaxWaitTime ; i++) {
	if (mIrDAState == kIrDAStatusOff) {
	    break;
	}
	[self _testSleep:1];
    }
    [self _testSleep:2];	// some settle time before reconnect
}

// Only one test implemented, so inTestToRun is ignored.  And inDuration is also ignored, as the SystemUIServer
// will loop calling all of it's menu extras until the total duration time has elapsed.  We need to return after
// a single test to give equal time to the other menu extras.

- (void) runSelfTest:(unsigned int)inTestToRun duration:(NSTimeInterval)inDuration
{
    static int iteration = 0;
    iteration++;
    
    [self _testrun:iteration];	    // run an on/off sequence
    
    // given we can't tell which is the last iteration, we're not
    // bothering to restore state after the last test run.
}



@end