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