Extras/HardwareGrowler/AppController.m
author Peter Hosey
Tue May 05 00:40:54 2009 -0700 (2009-05-05)
changeset 4204 2564276c9d5c
parent 4144 c78b283d2fca
child 4205 f3398597ecdd
permissions -rw-r--r--
Add new power icons for battery, AC adapter, and charging from AC adapter. Includes the original Opacity document that generates these icons.
     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 static CFDataRef powerBatteryIcon(void)
   189 {
   190 	static CFDataRef batteryIconData = NULL;
   191 	char imagePath[PATH_MAX];
   192 
   193 	if (!batteryIconData) {
   194 		CFURLRef imageURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
   195 													CFSTR("Power-Battery"),
   196 													CFSTR("png"),
   197 													/*subDirName*/ NULL);
   198 		if (CFURLGetFileSystemRepresentation(imageURL, false, (UInt8 *)imagePath, sizeof(imagePath)))
   199 			batteryIconData = (CFDataRef)readFile(imagePath);
   200 		CFRelease(imageURL);
   201 	}
   202 
   203 	return batteryIconData;
   204 }
   205 
   206 static CFDataRef powerACIcon(void)
   207 {
   208 	static CFDataRef ACPowerIconData = NULL;
   209 	char imagePath[PATH_MAX];
   210 
   211 	if (!ACPowerIconData) {
   212 		CFURLRef imageURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
   213 													CFSTR("Power-AC"),
   214 													CFSTR("png"),
   215 													/*subDirName*/ NULL);
   216 		if (CFURLGetFileSystemRepresentation(imageURL, false, (UInt8 *)imagePath, sizeof(imagePath)))
   217 			ACPowerIconData = (CFDataRef)readFile(imagePath);
   218 		CFRelease(imageURL);
   219 	}
   220 
   221 	return ACPowerIconData;
   222 }
   223 
   224 static CFDataRef powerACChargingIcon(void)
   225 {
   226 	static CFDataRef ACChargingPowerIconData = NULL;
   227 	char imagePath[PATH_MAX];
   228 
   229 	if (!ACChargingPowerIconData) {
   230 		CFURLRef imageURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
   231 													CFSTR("Power-ACCharging"),
   232 													CFSTR("png"),
   233 													/*subDirName*/ NULL);
   234 		if (CFURLGetFileSystemRepresentation(imageURL, false, (UInt8 *)imagePath, sizeof(imagePath)))
   235 			ACChargingPowerIconData = (CFDataRef)readFile(imagePath);
   236 		CFRelease(imageURL);
   237 	}
   238 
   239 	return ACChargingPowerIconData;
   240 }
   241 
   242 #pragma mark Firewire
   243 
   244 void AppController_fwDidConnect(CFStringRef deviceName) {
   245 //	NSLog(@"FireWire Connect: %@", deviceName);
   246 
   247 	CFStringRef title = NotifierFireWireConnectionTitle();
   248 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   249 							description:(NSString *)deviceName
   250 							notificationName:(NSString *)NotifierFireWireConnectionNotification
   251 							iconData:(NSData *)firewireLogo()
   252 							priority:0
   253 							isSticky:NO
   254 							clickContext:nil];
   255 	CFRelease(title);
   256 }
   257 
   258 void AppController_fwDidDisconnect(CFStringRef deviceName) {
   259 //	NSLog(@"FireWire Disconnect: %@", deviceName);
   260 
   261 	CFStringRef title = NotifierFireWireDisconnectionTitle();
   262 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   263 							description:(NSString *)deviceName
   264 							notificationName:(NSString *)NotifierFireWireDisconnectionNotification
   265 							iconData:(NSData *)firewireLogo()
   266 							priority:0
   267 							isSticky:NO
   268 							clickContext:nil];
   269 	CFRelease(title);
   270 }
   271 
   272 #pragma mark USB
   273 
   274 void AppController_usbDidConnect(CFStringRef deviceName) {
   275 //	NSLog(@"USB Connect: %@", deviceName);
   276 	CFStringRef title = NotifierUSBConnectionTitle();
   277 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   278 							description:(NSString *)deviceName
   279 							notificationName:(NSString *)NotifierUSBConnectionNotification
   280 							iconData:(NSData *)usbLogo()
   281 							priority:0
   282 							isSticky:NO
   283 							clickContext:nil];
   284 	CFRelease(title);
   285 }
   286 
   287 void AppController_usbDidDisconnect(CFStringRef deviceName) {
   288 //	NSLog(@"USB Disconnect: %@", deviceName);
   289 	CFStringRef title = NotifierUSBDisconnectionTitle();
   290 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   291 							description:(NSString *)deviceName
   292 							notificationName:(NSString *)NotifierUSBDisconnectionNotification
   293 							iconData:(NSData *)usbLogo()
   294 							priority:0
   295 							isSticky:NO
   296 							clickContext:nil];
   297 	CFRelease(title);
   298 }
   299 
   300 #pragma mark Bluetooth
   301 
   302 void AppController_bluetoothDidConnect(CFStringRef device) {
   303 //	NSLog(@"Bluetooth Connect: %@", device);
   304 	CFStringRef title = NotifierBluetoothConnectionTitle();
   305 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   306 							description:(NSString *)device
   307 							notificationName:(NSString *)NotifierBluetoothConnectionNotification
   308 							iconData:(NSData *)bluetoothLogo()
   309 							priority:0
   310 							isSticky:NO
   311 							clickContext:nil];
   312 	CFRelease(title);
   313 }
   314 
   315 void AppController_bluetoothDidDisconnect(CFStringRef device) {
   316 //	NSLog(@"Bluetooth Disconnect: %@", device);
   317 	CFStringRef title = NotifierBluetoothDisconnectionTitle();
   318 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   319 							description:(NSString *)device
   320 							notificationName:(NSString *)NotifierBluetoothDisconnectionNotification
   321 							iconData:(NSData *)bluetoothLogo()
   322 							priority:0
   323 							isSticky:NO
   324 							clickContext:nil];
   325 	CFRelease(title);
   326 }
   327 
   328 #pragma mark Volumes
   329 
   330 void AppController_volumeDidMount(VolumeInfo *info) {
   331 //	NSLog(@"volume Mount: %@", info);
   332 
   333 	CFStringRef title = NotifierVolumeMountedTitle();
   334 	NSDictionary *context = nil;
   335 	
   336 	if ([info path]) {
   337 		context = [NSDictionary dictionaryWithObjectsAndKeys:
   338 								(NSString *)NotifierVolumeMountedNotification, @"notification",
   339 								[info path], @"path",
   340 								nil];
   341 	}
   342 	
   343 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   344 							description:[info name]
   345 							notificationName:(NSString *)NotifierVolumeMountedNotification
   346 							iconData:[info iconData]
   347 							priority:0
   348 							isSticky:NO
   349 							clickContext:context];
   350 	CFRelease(title);
   351 }
   352 
   353 void AppController_volumeDidUnmount(VolumeInfo *info) {
   354 //	NSLog(@"volume Unmount: %@", info);
   355 
   356 	CFStringRef title = NotifierVolumeUnmountedTitle();
   357 
   358 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   359 							description:[info name]
   360 							notificationName:(NSString *)NotifierVolumeUnmountedNotification
   361 							iconData:[info iconData]
   362 							priority:0
   363 							isSticky:NO
   364 							clickContext:nil];
   365 	CFRelease(title);
   366 }
   367 
   368 #pragma mark Network
   369 
   370 void AppController_airportConnect(CFStringRef networkName, const unsigned char *bssidBytes) {
   371 	//NSLog(@"AirPort connect: %@", description);
   372 
   373 	if (sleeping)
   374 		return;
   375 
   376 	CFStringRef title = NotifierNetworkAirportConnectTitle();
   377 	
   378 	NSString *bssid = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",
   379 					   bssidBytes[0],
   380 					   bssidBytes[1],
   381 					   bssidBytes[2],
   382 					   bssidBytes[3],
   383 					   bssidBytes[4],
   384 					   bssidBytes[5]];
   385 	NSString *description = [NSString stringWithFormat:NSLocalizedString(@"Joined network.\nSSID:\t\t%@\nBSSID:\t%@", ""),
   386 							 networkName,
   387 							 bssid];
   388 
   389 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   390 								description:description
   391 						   notificationName:(NSString *)NotifierNetworkAirportConnectNotification
   392 								   iconData:(NSData *)airportIcon()
   393 								   priority:0
   394 								   isSticky:NO
   395 							   clickContext:nil];
   396 	CFRelease(title);
   397 }
   398 
   399 void AppController_airportDisconnect(CFStringRef networkName) {
   400 	//NSLog(@"AirPort disconnect: %@", description);
   401 
   402 	if (sleeping)
   403 		return;
   404 
   405 	CFStringRef title = NotifierNetworkAirportDisconnectTitle();
   406 	CFStringRef format = NotifierNetworkAirportDisconnectDescription();
   407 	CFStringRef description = CFStringCreateWithFormat(kCFAllocatorDefault,
   408 													   NULL,
   409 													   format,
   410 													   networkName);
   411 	CFRelease(format);
   412 
   413 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   414 								description:(NSString *)description
   415 						   notificationName:(NSString *)NotifierNetworkAirportDisconnectNotification
   416 								   iconData:(NSData *)airportIcon()
   417 								   priority:0
   418 								   isSticky:NO
   419 							   clickContext:nil];
   420 
   421 	CFRelease(title);
   422 	CFRelease(description);
   423 }
   424 
   425 void AppController_linkUp(CFStringRef description) {
   426 	//NSLog(@"Link up: %@", description);
   427 
   428 	if (sleeping)
   429 		return;
   430 
   431 	CFStringRef title = NotifierNetworkLinkUpTitle();
   432 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   433 								description:(NSString *)description
   434 						   notificationName:(NSString *)NotifierNetworkLinkUpNotification
   435 								   iconData:(NSData *)ipIcon()
   436 								   priority:0
   437 								   isSticky:NO
   438 							   clickContext:nil];
   439 	CFRelease(title);
   440 }
   441 
   442 void AppController_linkDown(CFStringRef description) {
   443 	//NSLog(@"Link down: %@", description);
   444 
   445 	if (sleeping)
   446 		return;
   447 
   448 	CFStringRef title = NotifierNetworkLinkDownTitle();
   449 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   450 								description:(NSString *)description
   451 						   notificationName:(NSString *)NotifierNetworkLinkDownNotification
   452 								   iconData:(NSData *)ipIcon()
   453 								   priority:0
   454 								   isSticky:NO
   455 							   clickContext:nil];
   456 	CFRelease(title);
   457 }
   458 
   459 void AppController_ipAcquired(CFStringRef ip, CFStringRef type) {
   460 	//NSLog(@"IP acquired: %@", ip);
   461 
   462 	if (sleeping)
   463 		return;
   464 
   465 	CFStringRef title = NotifierNetworkIpAcquiredTitle();
   466 	CFStringRef format = NotifierNetworkIpAcquiredDescription();
   467 	CFStringRef description = CFStringCreateWithFormat(kCFAllocatorDefault,
   468 													   NULL,
   469 													   format,
   470 													   ip,
   471 													   type);
   472 	CFRelease(format);
   473 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   474 								description:(NSString *)description
   475 						   notificationName:(NSString *)NotifierNetworkIpAcquiredNotification
   476 								   iconData:(NSData *)ipIcon()
   477 								   priority:0
   478 								   isSticky:NO
   479 							   clickContext:nil];
   480 	CFRelease(title);
   481 	CFRelease(description);
   482 }
   483 
   484 void AppController_ipReleased(void) {
   485 	//NSLog(@"IP released");
   486 
   487 	if (sleeping)
   488 		return;
   489 
   490 	CFStringRef title = NotifierNetworkIpReleasedTitle();
   491 	CFStringRef description = NotifierNetworkIpReleasedDescription();
   492 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   493 								description:(NSString *)description
   494 						   notificationName:(NSString *)NotifierNetworkIpReleasedNotification
   495 								   iconData:(NSData *)ipIcon()
   496 								   priority:0
   497 								   isSticky:NO
   498 							   clickContext:nil];
   499 	CFRelease(title);
   500 	CFRelease(description);
   501 }
   502 
   503 #pragma mark Sync
   504 
   505 void AppController_syncStarted(void) {
   506 	//NSLog(@"Sync started");
   507 
   508 	CFStringRef title = NotifierSyncStartedTitle();
   509 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   510 								description:(NSString *)title
   511 						   notificationName:(NSString *)NotifierSyncStartedNotification
   512 								   iconData:(NSData *)iSyncIcon()
   513 								   priority:0
   514 								   isSticky:NO
   515 							   clickContext:nil];
   516 	CFRelease(title);
   517 }
   518 
   519 void AppController_syncFinished(void) {
   520 	//NSLog(@"Sync finished");
   521 
   522 	CFStringRef title = NotifierSyncFinishedTitle();
   523 	[GrowlApplicationBridge notifyWithTitle:(NSString *)title
   524 								description:(NSString *)title
   525 						   notificationName:(NSString *)NotifierSyncFinishedNotification
   526 								   iconData:(NSData *)iSyncIcon()
   527 								   priority:0
   528 								   isSticky:NO
   529 							   clickContext:nil];
   530 	CFRelease(title);
   531 }
   532 
   533 #pragma mark Power
   534 void AppController_powerSwitched(HGPowerSource powerSource, CFBooleanRef isCharging,
   535 								 int batteryTime, int batteryPercentage)
   536 {
   537 	NSString		*title = nil;
   538 	NSMutableString *description = [NSMutableString string];
   539 	NSString		*notificationName = nil;
   540 	CFDataRef		imageData = NULL;
   541 
   542 	BOOL		haveBatteryTime = (batteryTime != -1);
   543 	BOOL		haveBatteryPercentage = (batteryPercentage != -1);
   544 
   545 	if (powerSource == HGACPower) {
   546 		title = NSLocalizedString(@"On A/C power", nil);
   547 
   548 		if (isCharging == kCFBooleanTrue) {
   549 			[description appendString:NSLocalizedString(@"Battery charging...", nil)];
   550 			if (haveBatteryTime || haveBatteryPercentage) [description appendString:@"\n"];
   551 			if (haveBatteryTime) [description appendFormat:NSLocalizedString(@"Time to charge: %i", nil), batteryTime];
   552 			if (haveBatteryTime && haveBatteryPercentage) [description appendString:@"\n"];
   553 			if (haveBatteryPercentage) [description appendFormat:NSLocalizedString(@"Current charge: %d%%", nil), batteryPercentage];
   554 			imageData = powerACChargingIcon();
   555 		} else {
   556 			imageData = powerACIcon();
   557 		}
   558 
   559 		notificationName = (NSString *)NotifierPowerOnACNotification;
   560 
   561 	} else if (powerSource == HGBatteryPower) {
   562 		title = NSLocalizedString(@"On battery power", nil);
   563 
   564 		if (haveBatteryTime) [description appendFormat:NSLocalizedString(@"Time remaining: %i minutes", nil), batteryTime];
   565 		if (haveBatteryTime && haveBatteryPercentage) [description appendString:@"\n"];
   566 		if (haveBatteryPercentage) [description appendFormat:NSLocalizedString(@"Current charge: %d%%", nil), batteryPercentage];
   567 
   568 		notificationName = (NSString *)NotifierPowerOnBatteryNotification;
   569 
   570 		imageData = powerBatteryIcon();
   571 
   572 	} else if (powerSource == HGUPSPower) {
   573 		title = NSLocalizedString(@"On UPS power", nil);
   574 
   575 		notificationName = (NSString *)NotifierPowerOnUPSNotification;
   576 	}
   577 
   578 	if (notificationName)
   579 		[GrowlApplicationBridge notifyWithTitle:title
   580 									description:description
   581 							   notificationName:notificationName
   582 									   iconData:(NSData *)imageData
   583 									   priority:0
   584 									   isSticky:NO
   585 								   clickContext:nil];
   586 }
   587 
   588 static void powerCallback(void *refcon, io_service_t service, natural_t messageType, void *messageArgument) {
   589 #pragma unused(refcon,service)
   590 	switch (messageType) {
   591 		case kIOMessageSystemWillRestart:
   592 		case kIOMessageSystemWillPowerOff:
   593 		case kIOMessageSystemWillSleep:
   594 		case kIOMessageDeviceWillPowerOff:
   595 			sleeping = YES;
   596 			IOAllowPowerChange(powerConnection, (long)messageArgument);
   597 			break;
   598 		case kIOMessageCanSystemPowerOff:
   599 		case kIOMessageCanSystemSleep:
   600 		case kIOMessageCanDevicePowerOff:
   601 			IOAllowPowerChange(powerConnection, (long)messageArgument);
   602 			break;
   603 		case kIOMessageSystemWillNotSleep:
   604 		case kIOMessageSystemWillNotPowerOff:
   605 		case kIOMessageSystemHasPoweredOn:
   606 		case kIOMessageDeviceWillNotPowerOff:
   607 		case kIOMessageDeviceHasPoweredOn:
   608 			sleeping = NO;
   609 		default:
   610 			break;
   611 	}
   612 }
   613 
   614 @implementation AppController
   615 
   616 - (void) awakeFromNib {
   617 	// Register ourselves as a Growl delegate for registration purposes
   618 	[GrowlApplicationBridge setGrowlDelegate:self];
   619 
   620 	// Register for sleep and wake notifications so we can suppress various notifications during sleep
   621 	IONotificationPortRef ioNotificationPort;
   622 	powerConnection = IORegisterForSystemPower(NULL, &ioNotificationPort, powerCallback, &powerNotifier);
   623 	if (powerConnection) {
   624 		powerRunLoopSource = IONotificationPortGetRunLoopSource(ioNotificationPort);
   625 		CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRunLoopSource, kCFRunLoopDefaultMode);
   626 	}
   627 
   628 	FireWireNotifier_init();
   629 	USBNotifier_init();
   630 	VolumeNotifier_init();
   631 	SyncNotifier_init();
   632 	BluetoothNotifier_init();
   633 	networkNotifier = [[NetworkNotifier alloc] init];
   634 	PowerNotifier_init();
   635 }
   636 
   637 - (void) dealloc {
   638 	FireWireNotifier_dealloc();
   639 	USBNotifier_dealloc();
   640 	VolumeNotifier_dealloc();
   641 	SyncNotifier_dealloc();
   642 	BluetoothNotifier_dealloc();
   643 	[networkNotifier release];
   644 
   645 	if (powerConnection) {
   646 		CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRunLoopSource, kCFRunLoopDefaultMode);
   647 		IODeregisterForSystemPower(&powerNotifier);
   648 	}
   649 
   650 	[super dealloc];
   651 }
   652 
   653 - (NSString *) applicationNameForGrowl {
   654 	return @"HardwareGrowler";
   655 }
   656 
   657 - (NSDictionary *) registrationDictionaryForGrowl {
   658 	NSDictionary *notificationsWithDescriptions = [NSDictionary dictionaryWithObjectsAndKeys:
   659 	 NotifierUSBConnectionHumanReadableDescription, NotifierUSBConnectionNotification,			
   660 	 NotifierUSBDisconnectionHumanReadableDescription, NotifierUSBDisconnectionNotification,		
   661 	 NotifierVolumeMountedHumanReadableDescription, NotifierVolumeMountedNotification,			
   662 	 NotifierVolumeUnmountedHumanReadableDescription, NotifierVolumeUnmountedNotification,			
   663 	 NotifierBluetoothConnectionHumanReadableDescription, NotifierBluetoothConnectionNotification,		
   664 	 NotifierBluetoothDisconnectionHumanReadableDescription, NotifierBluetoothDisconnectionNotification,	
   665 	 NotifierFireWireConnectionHumanReadableDescription, NotifierFireWireConnectionNotification,		
   666 	 NotifierFireWireDisconnectionHumanReadableDescription, NotifierFireWireDisconnectionNotification,	
   667 	 NotifierNetworkLinkUpHumanReadableDescription, NotifierNetworkLinkUpNotification,			
   668 	 NotifierNetworkLinkDownHumanReadableDescription, NotifierNetworkLinkDownNotification,			
   669 	 NotifierNetworkIpAcquiredHumanReadableDescription, NotifierNetworkIpAcquiredNotification,		
   670 	 NotifierNetworkIpReleasedHumanReadableDescription, NotifierNetworkIpReleasedNotification,		
   671 	 NotifierNetworkAirportConnectHumanReadableDescription, NotifierNetworkAirportConnectNotification,	
   672 	 NotifierNetworkAirportDisconnectHumanReadableDescription, NotifierNetworkAirportDisconnectNotification,
   673 	 NotifierSyncStartedHumanReadableDescription, NotifierSyncStartedNotification,				
   674 	 NotifierSyncFinishedHumanReadableDescription, NotifierSyncFinishedNotification,			
   675 	 NotifierPowerOnACHumanReadableDescription, NotifierPowerOnACNotification,				
   676 	 NotifierPowerOnBatteryHumanReadableDescription, NotifierPowerOnBatteryNotification,			
   677 	 NotifierPowerOnUPSHumanReadableDescription, NotifierPowerOnUPSNotification,				
   678 	 nil];
   679 
   680 	NSArray *allNotifications = [notificationsWithDescriptions allKeys];
   681 	
   682 	//Don't turn the sync notiifications on by default; they're noisy and not all that interesting.
   683 	NSMutableArray *defaultNotifications = [allNotifications mutableCopy];
   684 	[defaultNotifications removeObject:NotifierSyncStartedNotification];
   685 	[defaultNotifications removeObject:NotifierSyncFinishedNotification];
   686 	
   687 	NSDictionary *regDict = [NSDictionary dictionaryWithObjectsAndKeys:
   688 							 @"HardwareGrowler", GROWL_APP_NAME,
   689 							 allNotifications, GROWL_NOTIFICATIONS_ALL,
   690 							 defaultNotifications,	GROWL_NOTIFICATIONS_DEFAULT,
   691 							 notificationsWithDescriptions,	GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES,
   692 							 nil];
   693 
   694 	[defaultNotifications release];
   695 
   696 	return regDict;
   697 }
   698 
   699 - (void) growlNotificationWasClicked:(id)clickContext {
   700 	if ([[clickContext objectForKey:@"notification"] isEqualToString:(NSString *)NotifierVolumeMountedNotification])
   701 		[[NSWorkspace sharedWorkspace] openFile:[clickContext objectForKey:@"path"]];
   702 }
   703 
   704 - (IBAction) doSimpleHelp:(id)sender {
   705 #pragma unused(sender)
   706 	[[NSWorkspace sharedWorkspace] openFile:[[NSBundle mainBundle] pathForResource:@"readme" ofType:@"txt"]];
   707 }
   708 
   709 @end