Extras/HardwareGrowler/AppController.m
author diggory
Mon Jul 07 19:34:48 2008 +0000 (2008-07-07)
changeset 4144 c78b283d2fca
parent 3851 2185cda12bfa
child 4204 2564276c9d5c
permissions -rw-r--r--
HardwareGrowler now shows appropriate icons for Airport and Network events - the older ones no longer exist on more recent systems.
     1 #import "AppController.h"
     2 #include <IOKit/IOKitLib.h>
     3 #include <IOKit/IOMessage.h>
     4 #include <IOKit/pwr_mgt/IOPMLib.h>
     5 #include <CFGrowlAdditions.h>
     6 #include "FireWireNotifier.h"
     7 #include "USBNotifier.h"
     8 #include "BluetoothNotifier.h"
     9 #include "VolumeNotifier.h"
    10 #include "NetworkNotifier.h"
    11 #include "SyncNotifier.h"
    12 #include "PowerNotifier.h"
    13 
    14 #define NotifierUSBConnectionNotification				@"USB Device Connected"
    15 #define NotifierUSBDisconnectionNotification			@"USB Device Disconnected"
    16 #define NotifierVolumeMountedNotification				@"Volume Mounted"
    17 #define NotifierVolumeUnmountedNotification				@"Volume Unmounted"
    18 #define NotifierBluetoothConnectionNotification			@"Bluetooth Device Connected"
    19 #define NotifierBluetoothDisconnectionNotification		@"Bluetooth Device Disconnected"
    20 #define NotifierFireWireConnectionNotification			@"FireWire Device Connected"
    21 #define NotifierFireWireDisconnectionNotification		@"FireWire Device Disconnected"
    22 #define NotifierNetworkLinkUpNotification				@"Network Link Up"
    23 #define NotifierNetworkLinkDownNotification				@"Network Link Down"
    24 #define NotifierNetworkIpAcquiredNotification			@"IP Acquired"
    25 #define NotifierNetworkIpReleasedNotification			@"IP Released"
    26 #define NotifierNetworkAirportConnectNotification		@"AirPort Connected"
    27 #define NotifierNetworkAirportDisconnectNotification	@"AirPort Disconnected"
    28 #define NotifierSyncStartedNotification					@"Sync started"
    29 #define NotifierSyncFinishedNotification				@"Sync finished"
    30 #define NotifierPowerOnACNotification					@"Switched to A/C Power"
    31 #define NotifierPowerOnBatteryNotification				@"Switched to Battery Power"
    32 #define NotifierPowerOnUPSNotification					@"Switched to UPS Power"
    33 
    34 #define NotifierUSBConnectionHumanReadableDescription				NSLocalizedString(@"USB Device Connected", "")
    35 #define NotifierUSBDisconnectionHumanReadableDescription			NSLocalizedString(@"USB Device Disconnected", "")
    36 #define NotifierVolumeMountedHumanReadableDescription				NSLocalizedString(@"Volume Mounted", "")
    37 #define NotifierVolumeUnmountedHumanReadableDescription				NSLocalizedString(@"Volume Unmounted", "")
    38 #define NotifierBluetoothConnectionHumanReadableDescription			NSLocalizedString(@"Bluetooth Device Connected", "")
    39 #define NotifierBluetoothDisconnectionHumanReadableDescription		NSLocalizedString(@"Bluetooth Device Disconnected", "")
    40 #define NotifierFireWireConnectionHumanReadableDescription			NSLocalizedString(@"FireWire Device Connected", "")
    41 #define NotifierFireWireDisconnectionHumanReadableDescription		NSLocalizedString(@"FireWire Device Disconnected", "")
    42 #define NotifierNetworkLinkUpHumanReadableDescription				NSLocalizedString(@"Network Link Up", "")
    43 #define NotifierNetworkLinkDownHumanReadableDescription				NSLocalizedString(@"Network Link Down", "")
    44 #define NotifierNetworkIpAcquiredHumanReadableDescription			NSLocalizedString(@"IP Acquired", "")
    45 #define NotifierNetworkIpReleasedHumanReadableDescription			NSLocalizedString(@"IP Released", "")
    46 #define NotifierNetworkAirportConnectHumanReadableDescription		NSLocalizedString(@"AirPort Connected", "")
    47 #define NotifierNetworkAirportDisconnectHumanReadableDescription	NSLocalizedString(@"AirPort Disconnected", "")
    48 #define NotifierSyncStartedHumanReadableDescription					NSLocalizedString(@"Sync started", "")
    49 #define NotifierSyncFinishedHumanReadableDescription				NSLocalizedString(@"Sync finished", "")
    50 #define NotifierPowerOnACHumanReadableDescription					NSLocalizedString(@"Switched to A/C Power", "")
    51 #define NotifierPowerOnBatteryHumanReadableDescription				NSLocalizedString(@"Switched to Battery Power", "")
    52 #define NotifierPowerOnUPSHumanReadableDescription					NSLocalizedString(@"Switched to UPS Power", "")
    53 
    54 
    55 #define NotifierFireWireConnectionTitle()				CFCopyLocalizedString(CFSTR("FireWire Connection"), "")
    56 #define NotifierFireWireDisconnectionTitle()			CFCopyLocalizedString(CFSTR("FireWire Disconnection"), "")
    57 #define NotifierUSBConnectionTitle()					CFCopyLocalizedString(CFSTR("USB Connection"), "")
    58 #define NotifierUSBDisconnectionTitle()					CFCopyLocalizedString(CFSTR("USB Disconnection"), "")
    59 #define NotifierBluetoothConnectionTitle()				CFCopyLocalizedString(CFSTR("Bluetooth Connection"), "")
    60 #define NotifierBluetoothDisconnectionTitle()			CFCopyLocalizedString(CFSTR("Bluetooth Disconnection"), "")
    61 #define NotifierVolumeMountedTitle()					CFCopyLocalizedString(CFSTR("Volume Mounted"), "")
    62 #define NotifierVolumeUnmountedTitle()					CFCopyLocalizedString(CFSTR("Volume Unmounted"), "")
    63 #define NotifierNetworkAirportConnectTitle()			CFCopyLocalizedString(CFSTR("Airport connected"), "")
    64 #define NotifierNetworkAirportDisconnectTitle()			CFCopyLocalizedString(CFSTR("Airport disconnected"), "")
    65 #define NotifierNetworkLinkUpTitle()					CFCopyLocalizedString(CFSTR("Ethernet activated"), "")
    66 #define NotifierNetworkLinkDownTitle()					CFCopyLocalizedString(CFSTR("Ethernet deactivated"), "")
    67 #define NotifierNetworkIpAcquiredTitle()				CFCopyLocalizedString(CFSTR("IP address acquired"), "")
    68 #define NotifierNetworkIpReleasedTitle()				CFCopyLocalizedString(CFSTR("IP address released"), "")
    69 #define NotifierSyncStartedTitle()						CFCopyLocalizedString(CFSTR("Sync started"), "")
    70 #define NotifierSyncFinishedTitle()						CFCopyLocalizedString(CFSTR("Sync finished"), "")
    71 
    72 #define NotifierNetworkAirportDisconnectDescription()	CFCopyLocalizedString(CFSTR("Left network %@."), "")
    73 #define NotifierNetworkIpAcquiredDescription()			CFCopyLocalizedString(CFSTR("New primary IP: %@ (%@)"), "")
    74 #define NotifierNetworkIpReleasedDescription()			CFCopyLocalizedString(CFSTR("No IP address now"), "")
    75 
    76 static io_connect_t			powerConnection;
    77 static io_object_t			powerNotifier;
    78 static CFRunLoopSourceRef	powerRunLoopSource;
    79 static BOOL					sleeping;
    80 
    81 #pragma mark Icons
    82 
    83 static CFDataRef firewireLogo(void)
    84 {
    85 	static CFDataRef firewireLogoData = NULL;
    86 	char imagePath[PATH_MAX];
    87 
    88 	if (!firewireLogoData) {
    89 		CFURLRef imageURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
    90 												 CFSTR("FireWireLogo"),
    91 												 CFSTR("png"),
    92 												 /*subDirName*/ NULL);
    93 		if (CFURLGetFileSystemRepresentation(imageURL, false, (UInt8 *)imagePath, sizeof(imagePath)))
    94 			firewireLogoData = (CFDataRef)readFile(imagePath);
    95 		CFRelease(imageURL);
    96 	}
    97 
    98 	return firewireLogoData;
    99 }
   100 
   101 static CFDataRef usbLogo(void)
   102 {
   103 	static CFDataRef usbLogoData = NULL;
   104 	char imagePath[PATH_MAX];
   105 
   106 	if (!usbLogoData) {
   107 		CFURLRef imageURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
   108 													CFSTR("usbLogoWhite"),
   109 													CFSTR("png"),
   110 													/*subDirName*/ NULL);
   111 		if (CFURLGetFileSystemRepresentation(imageURL, false, (UInt8 *)imagePath, sizeof(imagePath)))
   112 			usbLogoData = (CFDataRef)readFile(imagePath);
   113 		CFRelease(imageURL);
   114 	}
   115 
   116 	return usbLogoData;
   117 }
   118 
   119 static CFDataRef bluetoothLogo(void)
   120 {
   121 	static CFDataRef bluetoothLogoData = NULL;
   122 	char imagePath[PATH_MAX];
   123 
   124 	if (!bluetoothLogoData) {
   125 		CFURLRef imageURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
   126 													CFSTR("BluetoothLogo"),
   127 													CFSTR("png"),
   128 													/*subDirName*/ NULL);
   129 		if (CFURLGetFileSystemRepresentation(imageURL, false, (UInt8 *)imagePath, sizeof(imagePath)))
   130 			bluetoothLogoData = (CFDataRef)readFile(imagePath);
   131 		CFRelease(imageURL);
   132 	}
   133 
   134 	return bluetoothLogoData;
   135 }
   136 
   137 static CFDataRef airportIcon(void)
   138 {
   139 	static CFDataRef airportIconData = NULL;
   140 
   141 	if (!airportIconData) {
   142 		CFURLRef appURL = (CFURLRef)copyURLForApplication(@"Airport Utility.app");
   143 		if (appURL) {
   144 			airportIconData = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
   145 			CFRelease(appURL);
   146 		} else {
   147 			appURL = (CFURLRef)copyURLForApplication(@"Airport Admin Utility.app");
   148 			if (appURL) {
   149 				airportIconData = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
   150 				CFRelease(appURL);
   151 			}			
   152 		}
   153 	}
   154 
   155 	return airportIconData;
   156 }
   157 
   158 static CFDataRef ipIcon(void)
   159 {
   160 	static CFDataRef ipIconData = NULL;
   161 
   162 	if (!ipIconData) {
   163 		CFURLRef appURL = (CFURLRef)copyURLForApplication(@"Network Utility.app");
   164 		if (appURL) {
   165 			ipIconData = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
   166 			CFRelease(appURL);
   167 		}
   168 	}
   169 
   170 	return ipIconData;
   171 }
   172 
   173 static CFDataRef iSyncIcon(void)
   174 {
   175 	static CFDataRef iSyncIconData = NULL;
   176 
   177 	if (!iSyncIconData) {
   178 		CFURLRef appURL = (CFURLRef)copyURLForApplication(@"iSync.app");
   179 		if (appURL) {
   180 			iSyncIconData = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
   181 			CFRelease(appURL);
   182 		}
   183 	}
   184 
   185 	return iSyncIconData;
   186 }
   187 
   188 #pragma mark Firewire
   189 
   190 void AppController_fwDidConnect(CFStringRef deviceName) {
   191 //	NSLog(@"FireWire Connect: %@", deviceName);
   192 
   193 	CFStringRef title = NotifierFireWireConnectionTitle();
   194 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   195 							description:(NSString *)deviceName
   196 							notificationName:(NSString *)NotifierFireWireConnectionNotification
   197 							iconData:(NSData *)firewireLogo()
   198 							priority:0
   199 							isSticky:NO
   200 							clickContext:nil];
   201 	CFRelease(title);
   202 }
   203 
   204 void AppController_fwDidDisconnect(CFStringRef deviceName) {
   205 //	NSLog(@"FireWire Disconnect: %@", deviceName);
   206 
   207 	CFStringRef title = NotifierFireWireDisconnectionTitle();
   208 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   209 							description:(NSString *)deviceName
   210 							notificationName:(NSString *)NotifierFireWireDisconnectionNotification
   211 							iconData:(NSData *)firewireLogo()
   212 							priority:0
   213 							isSticky:NO
   214 							clickContext:nil];
   215 	CFRelease(title);
   216 }
   217 
   218 #pragma mark USB
   219 
   220 void AppController_usbDidConnect(CFStringRef deviceName) {
   221 //	NSLog(@"USB Connect: %@", deviceName);
   222 	CFStringRef title = NotifierUSBConnectionTitle();
   223 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   224 							description:(NSString *)deviceName
   225 							notificationName:(NSString *)NotifierUSBConnectionNotification
   226 							iconData:(NSData *)usbLogo()
   227 							priority:0
   228 							isSticky:NO
   229 							clickContext:nil];
   230 	CFRelease(title);
   231 }
   232 
   233 void AppController_usbDidDisconnect(CFStringRef deviceName) {
   234 //	NSLog(@"USB Disconnect: %@", deviceName);
   235 	CFStringRef title = NotifierUSBDisconnectionTitle();
   236 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   237 							description:(NSString *)deviceName
   238 							notificationName:(NSString *)NotifierUSBDisconnectionNotification
   239 							iconData:(NSData *)usbLogo()
   240 							priority:0
   241 							isSticky:NO
   242 							clickContext:nil];
   243 	CFRelease(title);
   244 }
   245 
   246 #pragma mark Bluetooth
   247 
   248 void AppController_bluetoothDidConnect(CFStringRef device) {
   249 //	NSLog(@"Bluetooth Connect: %@", device);
   250 	CFStringRef title = NotifierBluetoothConnectionTitle();
   251 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   252 							description:(NSString *)device
   253 							notificationName:(NSString *)NotifierBluetoothConnectionNotification
   254 							iconData:(NSData *)bluetoothLogo()
   255 							priority:0
   256 							isSticky:NO
   257 							clickContext:nil];
   258 	CFRelease(title);
   259 }
   260 
   261 void AppController_bluetoothDidDisconnect(CFStringRef device) {
   262 //	NSLog(@"Bluetooth Disconnect: %@", device);
   263 	CFStringRef title = NotifierBluetoothDisconnectionTitle();
   264 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   265 							description:(NSString *)device
   266 							notificationName:(NSString *)NotifierBluetoothDisconnectionNotification
   267 							iconData:(NSData *)bluetoothLogo()
   268 							priority:0
   269 							isSticky:NO
   270 							clickContext:nil];
   271 	CFRelease(title);
   272 }
   273 
   274 #pragma mark Volumes
   275 
   276 void AppController_volumeDidMount(VolumeInfo *info) {
   277 //	NSLog(@"volume Mount: %@", info);
   278 
   279 	CFStringRef title = NotifierVolumeMountedTitle();
   280 	NSDictionary *context = nil;
   281 	
   282 	if ([info path]) {
   283 		context = [NSDictionary dictionaryWithObjectsAndKeys:
   284 								(NSString *)NotifierVolumeMountedNotification, @"notification",
   285 								[info path], @"path",
   286 								nil];
   287 	}
   288 	
   289 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   290 							description:[info name]
   291 							notificationName:(NSString *)NotifierVolumeMountedNotification
   292 							iconData:[info iconData]
   293 							priority:0
   294 							isSticky:NO
   295 							clickContext:context];
   296 	CFRelease(title);
   297 }
   298 
   299 void AppController_volumeDidUnmount(VolumeInfo *info) {
   300 //	NSLog(@"volume Unmount: %@", info);
   301 
   302 	CFStringRef title = NotifierVolumeUnmountedTitle();
   303 
   304 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   305 							description:[info name]
   306 							notificationName:(NSString *)NotifierVolumeUnmountedNotification
   307 							iconData:[info iconData]
   308 							priority:0
   309 							isSticky:NO
   310 							clickContext:nil];
   311 	CFRelease(title);
   312 }
   313 
   314 #pragma mark Network
   315 
   316 void AppController_airportConnect(CFStringRef networkName, const unsigned char *bssidBytes) {
   317 	//NSLog(@"AirPort connect: %@", description);
   318 
   319 	if (sleeping)
   320 		return;
   321 
   322 	CFStringRef title = NotifierNetworkAirportConnectTitle();
   323 	
   324 	NSString *bssid = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",
   325 					   bssidBytes[0],
   326 					   bssidBytes[1],
   327 					   bssidBytes[2],
   328 					   bssidBytes[3],
   329 					   bssidBytes[4],
   330 					   bssidBytes[5]];
   331 	NSString *description = [NSString stringWithFormat:NSLocalizedString(@"Joined network.\nSSID:\t\t%@\nBSSID:\t%@", ""),
   332 							 networkName,
   333 							 bssid];
   334 
   335 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   336 								description:description
   337 						   notificationName:(NSString *)NotifierNetworkAirportConnectNotification
   338 								   iconData:(NSData *)airportIcon()
   339 								   priority:0
   340 								   isSticky:NO
   341 							   clickContext:nil];
   342 	CFRelease(title);
   343 }
   344 
   345 void AppController_airportDisconnect(CFStringRef networkName) {
   346 	//NSLog(@"AirPort disconnect: %@", description);
   347 
   348 	if (sleeping)
   349 		return;
   350 
   351 	CFStringRef title = NotifierNetworkAirportDisconnectTitle();
   352 	CFStringRef format = NotifierNetworkAirportDisconnectDescription();
   353 	CFStringRef description = CFStringCreateWithFormat(kCFAllocatorDefault,
   354 													   NULL,
   355 													   format,
   356 													   networkName);
   357 	CFRelease(format);
   358 
   359 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   360 								description:(NSString *)description
   361 						   notificationName:(NSString *)NotifierNetworkAirportDisconnectNotification
   362 								   iconData:(NSData *)airportIcon()
   363 								   priority:0
   364 								   isSticky:NO
   365 							   clickContext:nil];
   366 
   367 	CFRelease(title);
   368 	CFRelease(description);
   369 }
   370 
   371 void AppController_linkUp(CFStringRef description) {
   372 	//NSLog(@"Link up: %@", description);
   373 
   374 	if (sleeping)
   375 		return;
   376 
   377 	CFStringRef title = NotifierNetworkLinkUpTitle();
   378 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   379 								description:(NSString *)description
   380 						   notificationName:(NSString *)NotifierNetworkLinkUpNotification
   381 								   iconData:(NSData *)ipIcon()
   382 								   priority:0
   383 								   isSticky:NO
   384 							   clickContext:nil];
   385 	CFRelease(title);
   386 }
   387 
   388 void AppController_linkDown(CFStringRef description) {
   389 	//NSLog(@"Link down: %@", description);
   390 
   391 	if (sleeping)
   392 		return;
   393 
   394 	CFStringRef title = NotifierNetworkLinkDownTitle();
   395 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   396 								description:(NSString *)description
   397 						   notificationName:(NSString *)NotifierNetworkLinkDownNotification
   398 								   iconData:(NSData *)ipIcon()
   399 								   priority:0
   400 								   isSticky:NO
   401 							   clickContext:nil];
   402 	CFRelease(title);
   403 }
   404 
   405 void AppController_ipAcquired(CFStringRef ip, CFStringRef type) {
   406 	//NSLog(@"IP acquired: %@", ip);
   407 
   408 	if (sleeping)
   409 		return;
   410 
   411 	CFStringRef title = NotifierNetworkIpAcquiredTitle();
   412 	CFStringRef format = NotifierNetworkIpAcquiredDescription();
   413 	CFStringRef description = CFStringCreateWithFormat(kCFAllocatorDefault,
   414 													   NULL,
   415 													   format,
   416 													   ip,
   417 													   type);
   418 	CFRelease(format);
   419 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   420 								description:(NSString *)description
   421 						   notificationName:(NSString *)NotifierNetworkIpAcquiredNotification
   422 								   iconData:(NSData *)ipIcon()
   423 								   priority:0
   424 								   isSticky:NO
   425 							   clickContext:nil];
   426 	CFRelease(title);
   427 	CFRelease(description);
   428 }
   429 
   430 void AppController_ipReleased(void) {
   431 	//NSLog(@"IP released");
   432 
   433 	if (sleeping)
   434 		return;
   435 
   436 	CFStringRef title = NotifierNetworkIpReleasedTitle();
   437 	CFStringRef description = NotifierNetworkIpReleasedDescription();
   438 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   439 								description:(NSString *)description
   440 						   notificationName:(NSString *)NotifierNetworkIpReleasedNotification
   441 								   iconData:(NSData *)ipIcon()
   442 								   priority:0
   443 								   isSticky:NO
   444 							   clickContext:nil];
   445 	CFRelease(title);
   446 	CFRelease(description);
   447 }
   448 
   449 #pragma mark Sync
   450 
   451 void AppController_syncStarted(void) {
   452 	//NSLog(@"Sync started");
   453 
   454 	CFStringRef title = NotifierSyncStartedTitle();
   455 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   456 								description:(NSString *)title
   457 						   notificationName:(NSString *)NotifierSyncStartedNotification
   458 								   iconData:(NSData *)iSyncIcon()
   459 								   priority:0
   460 								   isSticky:NO
   461 							   clickContext:nil];
   462 	CFRelease(title);
   463 }
   464 
   465 void AppController_syncFinished(void) {
   466 	//NSLog(@"Sync finished");
   467 
   468 	CFStringRef title = NotifierSyncFinishedTitle();
   469 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   470 								description:(NSString *)title
   471 						   notificationName:(NSString *)NotifierSyncFinishedNotification
   472 								   iconData:(NSData *)iSyncIcon()
   473 								   priority:0
   474 								   isSticky:NO
   475 							   clickContext:nil];
   476 	CFRelease(title);
   477 }
   478 
   479 #pragma mark Power
   480 void AppController_powerSwitched(HGPowerSource powerSource, CFBooleanRef isCharging,
   481 								 int batteryTime, int batteryPercentage)
   482 {
   483 	NSString		*title = nil;
   484 	NSMutableString *description = [NSMutableString string];
   485 	NSString		*notificationName = nil;
   486 	CFDataRef		imageData = iSyncIcon();
   487 
   488 	BOOL		haveBatteryTime = (batteryTime != -1);
   489 	BOOL		haveBatteryPercentage = (batteryPercentage != -1);
   490 
   491 	if (powerSource == HGACPower) {
   492 		title = NSLocalizedString(@"On A/C power", nil);
   493 
   494 		if (isCharging == kCFBooleanTrue) {
   495 			[description appendString:NSLocalizedString(@"Battery charging...", nil)];
   496 			if (haveBatteryTime || haveBatteryPercentage) [description appendString:@"\n"];
   497 			if (haveBatteryTime) [description appendFormat:NSLocalizedString(@"Time to charge: %i", nil), batteryTime];
   498 			if (haveBatteryTime && haveBatteryPercentage) [description appendString:@"\n"];
   499 			if (haveBatteryPercentage) [description appendFormat:NSLocalizedString(@"Current charge: %d%%", nil), batteryPercentage];
   500 		}
   501 
   502 		notificationName = (NSString *)NotifierPowerOnACNotification;
   503 
   504 	} else if (powerSource == HGBatteryPower) {
   505 		title = NSLocalizedString(@"On battery power", nil);
   506 
   507 		if (haveBatteryTime) [description appendFormat:NSLocalizedString(@"Time remaining: %i minutes", nil), batteryTime];
   508 		if (haveBatteryTime && haveBatteryPercentage) [description appendString:@"\n"];
   509 		if (haveBatteryPercentage) [description appendFormat:NSLocalizedString(@"Current charge: %d%%", nil), batteryPercentage];
   510 
   511 		notificationName = (NSString *)NotifierPowerOnBatteryNotification;
   512 
   513 	} else if (powerSource == HGUPSPower) {
   514 		title = NSLocalizedString(@"On UPS power", nil);
   515 
   516 		notificationName = (NSString *)NotifierPowerOnUPSNotification;
   517 	}
   518 
   519 	if (notificationName)
   520 		[GrowlApplicationBridge notifyWithTitle:title
   521 									description:description
   522 							   notificationName:notificationName
   523 									   iconData:(NSData *)imageData
   524 									   priority:0
   525 									   isSticky:NO
   526 								   clickContext:nil];
   527 }
   528 
   529 static void powerCallback(void *refcon, io_service_t service, natural_t messageType, void *messageArgument) {
   530 #pragma unused(refcon,service)
   531 	switch (messageType) {
   532 		case kIOMessageSystemWillRestart:
   533 		case kIOMessageSystemWillPowerOff:
   534 		case kIOMessageSystemWillSleep:
   535 		case kIOMessageDeviceWillPowerOff:
   536 			sleeping = YES;
   537 			IOAllowPowerChange(powerConnection, (long)messageArgument);
   538 			break;
   539 		case kIOMessageCanSystemPowerOff:
   540 		case kIOMessageCanSystemSleep:
   541 		case kIOMessageCanDevicePowerOff:
   542 			IOAllowPowerChange(powerConnection, (long)messageArgument);
   543 			break;
   544 		case kIOMessageSystemWillNotSleep:
   545 		case kIOMessageSystemWillNotPowerOff:
   546 		case kIOMessageSystemHasPoweredOn:
   547 		case kIOMessageDeviceWillNotPowerOff:
   548 		case kIOMessageDeviceHasPoweredOn:
   549 			sleeping = NO;
   550 		default:
   551 			break;
   552 	}
   553 }
   554 
   555 @implementation AppController
   556 
   557 - (void) awakeFromNib {
   558 	// Register ourselves as a Growl delegate for registration purposes
   559 	[GrowlApplicationBridge setGrowlDelegate:self];
   560 
   561 	// Register for sleep and wake notifications so we can suppress various notifications during sleep
   562 	IONotificationPortRef ioNotificationPort;
   563 	powerConnection = IORegisterForSystemPower(NULL, &ioNotificationPort, powerCallback, &powerNotifier);
   564 	if (powerConnection) {
   565 		powerRunLoopSource = IONotificationPortGetRunLoopSource(ioNotificationPort);
   566 		CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRunLoopSource, kCFRunLoopDefaultMode);
   567 	}
   568 
   569 	FireWireNotifier_init();
   570 	USBNotifier_init();
   571 	VolumeNotifier_init();
   572 	SyncNotifier_init();
   573 	BluetoothNotifier_init();
   574 	networkNotifier = [[NetworkNotifier alloc] init];
   575 	PowerNotifier_init();
   576 }
   577 
   578 - (void) dealloc {
   579 	FireWireNotifier_dealloc();
   580 	USBNotifier_dealloc();
   581 	VolumeNotifier_dealloc();
   582 	SyncNotifier_dealloc();
   583 	BluetoothNotifier_dealloc();
   584 	[networkNotifier release];
   585 
   586 	if (powerConnection) {
   587 		CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRunLoopSource, kCFRunLoopDefaultMode);
   588 		IODeregisterForSystemPower(&powerNotifier);
   589 	}
   590 
   591 	[super dealloc];
   592 }
   593 
   594 - (NSString *) applicationNameForGrowl {
   595 	return @"HardwareGrowler";
   596 }
   597 
   598 - (NSDictionary *) registrationDictionaryForGrowl {
   599 	NSDictionary *notificationsWithDescriptions = [NSDictionary dictionaryWithObjectsAndKeys:
   600 	 NotifierUSBConnectionHumanReadableDescription, NotifierUSBConnectionNotification,			
   601 	 NotifierUSBDisconnectionHumanReadableDescription, NotifierUSBDisconnectionNotification,		
   602 	 NotifierVolumeMountedHumanReadableDescription, NotifierVolumeMountedNotification,			
   603 	 NotifierVolumeUnmountedHumanReadableDescription, NotifierVolumeUnmountedNotification,			
   604 	 NotifierBluetoothConnectionHumanReadableDescription, NotifierBluetoothConnectionNotification,		
   605 	 NotifierBluetoothDisconnectionHumanReadableDescription, NotifierBluetoothDisconnectionNotification,	
   606 	 NotifierFireWireConnectionHumanReadableDescription, NotifierFireWireConnectionNotification,		
   607 	 NotifierFireWireDisconnectionHumanReadableDescription, NotifierFireWireDisconnectionNotification,	
   608 	 NotifierNetworkLinkUpHumanReadableDescription, NotifierNetworkLinkUpNotification,			
   609 	 NotifierNetworkLinkDownHumanReadableDescription, NotifierNetworkLinkDownNotification,			
   610 	 NotifierNetworkIpAcquiredHumanReadableDescription, NotifierNetworkIpAcquiredNotification,		
   611 	 NotifierNetworkIpReleasedHumanReadableDescription, NotifierNetworkIpReleasedNotification,		
   612 	 NotifierNetworkAirportConnectHumanReadableDescription, NotifierNetworkAirportConnectNotification,	
   613 	 NotifierNetworkAirportDisconnectHumanReadableDescription, NotifierNetworkAirportDisconnectNotification,
   614 	 NotifierSyncStartedHumanReadableDescription, NotifierSyncStartedNotification,				
   615 	 NotifierSyncFinishedHumanReadableDescription, NotifierSyncFinishedNotification,			
   616 	 NotifierPowerOnACHumanReadableDescription, NotifierPowerOnACNotification,				
   617 	 NotifierPowerOnBatteryHumanReadableDescription, NotifierPowerOnBatteryNotification,			
   618 	 NotifierPowerOnUPSHumanReadableDescription, NotifierPowerOnUPSNotification,				
   619 	 nil];
   620 
   621 	NSArray *allNotifications = [notificationsWithDescriptions allKeys];
   622 	
   623 	//Don't turn the sync notiifications on by default; they're noisy and not all that interesting.
   624 	NSMutableArray *defaultNotifications = [allNotifications mutableCopy];
   625 	[defaultNotifications removeObject:NotifierSyncStartedNotification];
   626 	[defaultNotifications removeObject:NotifierSyncFinishedNotification];
   627 	
   628 	NSDictionary *regDict = [NSDictionary dictionaryWithObjectsAndKeys:
   629 							 @"HardwareGrowler", GROWL_APP_NAME,
   630 							 allNotifications, GROWL_NOTIFICATIONS_ALL,
   631 							 defaultNotifications,	GROWL_NOTIFICATIONS_DEFAULT,
   632 							 notificationsWithDescriptions,	GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES,
   633 							 nil];
   634 
   635 	[defaultNotifications release];
   636 
   637 	return regDict;
   638 }
   639 
   640 - (void) growlNotificationWasClicked:(id)clickContext {
   641 	if ([[clickContext objectForKey:@"notification"] isEqualToString:(NSString *)NotifierVolumeMountedNotification])
   642 		[[NSWorkspace sharedWorkspace] openFile:[clickContext objectForKey:@"path"]];
   643 }
   644 
   645 - (IBAction) doSimpleHelp:(id)sender {
   646 #pragma unused(sender)
   647 	[[NSWorkspace sharedWorkspace] openFile:[[NSBundle mainBundle] pathForResource:@"readme" ofType:@"txt"]];
   648 }
   649 
   650 @end