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"
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"
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", "")
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"), "")
72 #define NotifierNetworkAirportDisconnectDescription() CFCopyLocalizedString(CFSTR("Left network %@."), "")
73 #define NotifierNetworkIpAcquiredDescription() CFCopyLocalizedString(CFSTR("New primary IP: %@ (%@)"), "")
74 #define NotifierNetworkIpReleasedDescription() CFCopyLocalizedString(CFSTR("No IP address now"), "")
76 static io_connect_t powerConnection;
77 static io_object_t powerNotifier;
78 static CFRunLoopSourceRef powerRunLoopSource;
83 static CFDataRef firewireLogo(void)
85 static CFDataRef firewireLogoData = NULL;
86 char imagePath[PATH_MAX];
88 if (!firewireLogoData) {
89 CFURLRef imageURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
90 CFSTR("FireWireLogo"),
93 if (CFURLGetFileSystemRepresentation(imageURL, false, (UInt8 *)imagePath, sizeof(imagePath)))
94 firewireLogoData = (CFDataRef)readFile(imagePath);
98 return firewireLogoData;
101 static CFDataRef usbLogo(void)
103 static CFDataRef usbLogoData = NULL;
104 char imagePath[PATH_MAX];
107 CFURLRef imageURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
108 CFSTR("usbLogoWhite"),
110 /*subDirName*/ NULL);
111 if (CFURLGetFileSystemRepresentation(imageURL, false, (UInt8 *)imagePath, sizeof(imagePath)))
112 usbLogoData = (CFDataRef)readFile(imagePath);
119 static CFDataRef bluetoothLogo(void)
121 static CFDataRef bluetoothLogoData = NULL;
122 char imagePath[PATH_MAX];
124 if (!bluetoothLogoData) {
125 CFURLRef imageURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
126 CFSTR("BluetoothLogo"),
128 /*subDirName*/ NULL);
129 if (CFURLGetFileSystemRepresentation(imageURL, false, (UInt8 *)imagePath, sizeof(imagePath)))
130 bluetoothLogoData = (CFDataRef)readFile(imagePath);
134 return bluetoothLogoData;
137 static CFDataRef airportIcon(void)
139 static CFDataRef airportIconData = NULL;
141 if (!airportIconData) {
142 CFURLRef appURL = (CFURLRef)copyURLForApplication(@"Airport Utility.app");
144 airportIconData = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
147 appURL = (CFURLRef)copyURLForApplication(@"Airport Admin Utility.app");
149 airportIconData = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
155 return airportIconData;
158 static CFDataRef ipIcon(void)
160 static CFDataRef ipIconData = NULL;
163 CFURLRef appURL = (CFURLRef)copyURLForApplication(@"Network Utility.app");
165 ipIconData = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
173 static CFDataRef iSyncIcon(void)
175 static CFDataRef iSyncIconData = NULL;
177 if (!iSyncIconData) {
178 CFURLRef appURL = (CFURLRef)copyURLForApplication(@"iSync.app");
180 iSyncIconData = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
185 return iSyncIconData;
188 #pragma mark Firewire
190 void AppController_fwDidConnect(CFStringRef deviceName) {
191 // NSLog(@"FireWire Connect: %@", deviceName);
193 CFStringRef title = NotifierFireWireConnectionTitle();
194 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
195 description:(NSString *)deviceName
196 notificationName:(NSString *)NotifierFireWireConnectionNotification
197 iconData:(NSData *)firewireLogo()
204 void AppController_fwDidDisconnect(CFStringRef deviceName) {
205 // NSLog(@"FireWire Disconnect: %@", deviceName);
207 CFStringRef title = NotifierFireWireDisconnectionTitle();
208 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
209 description:(NSString *)deviceName
210 notificationName:(NSString *)NotifierFireWireDisconnectionNotification
211 iconData:(NSData *)firewireLogo()
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()
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()
246 #pragma mark Bluetooth
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()
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()
276 void AppController_volumeDidMount(VolumeInfo *info) {
277 // NSLog(@"volume Mount: %@", info);
279 CFStringRef title = NotifierVolumeMountedTitle();
280 NSDictionary *context = nil;
283 context = [NSDictionary dictionaryWithObjectsAndKeys:
284 (NSString *)NotifierVolumeMountedNotification, @"notification",
285 [info path], @"path",
289 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
290 description:[info name]
291 notificationName:(NSString *)NotifierVolumeMountedNotification
292 iconData:[info iconData]
295 clickContext:context];
299 void AppController_volumeDidUnmount(VolumeInfo *info) {
300 // NSLog(@"volume Unmount: %@", info);
302 CFStringRef title = NotifierVolumeUnmountedTitle();
304 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
305 description:[info name]
306 notificationName:(NSString *)NotifierVolumeUnmountedNotification
307 iconData:[info iconData]
316 void AppController_airportConnect(CFStringRef networkName, const unsigned char *bssidBytes) {
317 //NSLog(@"AirPort connect: %@", description);
322 CFStringRef title = NotifierNetworkAirportConnectTitle();
324 NSString *bssid = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",
331 NSString *description = [NSString stringWithFormat:NSLocalizedString(@"Joined network.\nSSID:\t\t%@\nBSSID:\t%@", ""),
335 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
336 description:description
337 notificationName:(NSString *)NotifierNetworkAirportConnectNotification
338 iconData:(NSData *)airportIcon()
345 void AppController_airportDisconnect(CFStringRef networkName) {
346 //NSLog(@"AirPort disconnect: %@", description);
351 CFStringRef title = NotifierNetworkAirportDisconnectTitle();
352 CFStringRef format = NotifierNetworkAirportDisconnectDescription();
353 CFStringRef description = CFStringCreateWithFormat(kCFAllocatorDefault,
359 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
360 description:(NSString *)description
361 notificationName:(NSString *)NotifierNetworkAirportDisconnectNotification
362 iconData:(NSData *)airportIcon()
368 CFRelease(description);
371 void AppController_linkUp(CFStringRef description) {
372 //NSLog(@"Link up: %@", description);
377 CFStringRef title = NotifierNetworkLinkUpTitle();
378 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
379 description:(NSString *)description
380 notificationName:(NSString *)NotifierNetworkLinkUpNotification
381 iconData:(NSData *)ipIcon()
388 void AppController_linkDown(CFStringRef description) {
389 //NSLog(@"Link down: %@", description);
394 CFStringRef title = NotifierNetworkLinkDownTitle();
395 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
396 description:(NSString *)description
397 notificationName:(NSString *)NotifierNetworkLinkDownNotification
398 iconData:(NSData *)ipIcon()
405 void AppController_ipAcquired(CFStringRef ip, CFStringRef type) {
406 //NSLog(@"IP acquired: %@", ip);
411 CFStringRef title = NotifierNetworkIpAcquiredTitle();
412 CFStringRef format = NotifierNetworkIpAcquiredDescription();
413 CFStringRef description = CFStringCreateWithFormat(kCFAllocatorDefault,
419 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
420 description:(NSString *)description
421 notificationName:(NSString *)NotifierNetworkIpAcquiredNotification
422 iconData:(NSData *)ipIcon()
427 CFRelease(description);
430 void AppController_ipReleased(void) {
431 //NSLog(@"IP released");
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()
446 CFRelease(description);
451 void AppController_syncStarted(void) {
452 //NSLog(@"Sync started");
454 CFStringRef title = NotifierSyncStartedTitle();
455 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
456 description:(NSString *)title
457 notificationName:(NSString *)NotifierSyncStartedNotification
458 iconData:(NSData *)iSyncIcon()
465 void AppController_syncFinished(void) {
466 //NSLog(@"Sync finished");
468 CFStringRef title = NotifierSyncFinishedTitle();
469 [GrowlApplicationBridge notifyWithTitle:(NSString *)title
470 description:(NSString *)title
471 notificationName:(NSString *)NotifierSyncFinishedNotification
472 iconData:(NSData *)iSyncIcon()
480 void AppController_powerSwitched(HGPowerSource powerSource, CFBooleanRef isCharging,
481 int batteryTime, int batteryPercentage)
483 NSString *title = nil;
484 NSMutableString *description = [NSMutableString string];
485 NSString *notificationName = nil;
486 CFDataRef imageData = iSyncIcon();
488 BOOL haveBatteryTime = (batteryTime != -1);
489 BOOL haveBatteryPercentage = (batteryPercentage != -1);
491 if (powerSource == HGACPower) {
492 title = NSLocalizedString(@"On A/C power", nil);
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];
502 notificationName = (NSString *)NotifierPowerOnACNotification;
504 } else if (powerSource == HGBatteryPower) {
505 title = NSLocalizedString(@"On battery power", nil);
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];
511 notificationName = (NSString *)NotifierPowerOnBatteryNotification;
513 } else if (powerSource == HGUPSPower) {
514 title = NSLocalizedString(@"On UPS power", nil);
516 notificationName = (NSString *)NotifierPowerOnUPSNotification;
519 if (notificationName)
520 [GrowlApplicationBridge notifyWithTitle:title
521 description:description
522 notificationName:notificationName
523 iconData:(NSData *)imageData
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:
537 IOAllowPowerChange(powerConnection, (long)messageArgument);
539 case kIOMessageCanSystemPowerOff:
540 case kIOMessageCanSystemSleep:
541 case kIOMessageCanDevicePowerOff:
542 IOAllowPowerChange(powerConnection, (long)messageArgument);
544 case kIOMessageSystemWillNotSleep:
545 case kIOMessageSystemWillNotPowerOff:
546 case kIOMessageSystemHasPoweredOn:
547 case kIOMessageDeviceWillNotPowerOff:
548 case kIOMessageDeviceHasPoweredOn:
555 @implementation AppController
557 - (void) awakeFromNib {
558 // Register ourselves as a Growl delegate for registration purposes
559 [GrowlApplicationBridge setGrowlDelegate:self];
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);
569 FireWireNotifier_init();
571 VolumeNotifier_init();
573 BluetoothNotifier_init();
574 networkNotifier = [[NetworkNotifier alloc] init];
575 PowerNotifier_init();
579 FireWireNotifier_dealloc();
580 USBNotifier_dealloc();
581 VolumeNotifier_dealloc();
582 SyncNotifier_dealloc();
583 BluetoothNotifier_dealloc();
584 [networkNotifier release];
586 if (powerConnection) {
587 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRunLoopSource, kCFRunLoopDefaultMode);
588 IODeregisterForSystemPower(&powerNotifier);
594 - (NSString *) applicationNameForGrowl {
595 return @"HardwareGrowler";
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,
621 NSArray *allNotifications = [notificationsWithDescriptions allKeys];
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];
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,
635 [defaultNotifications release];
640 - (void) growlNotificationWasClicked:(id)clickContext {
641 if ([[clickContext objectForKey:@"notification"] isEqualToString:(NSString *)NotifierVolumeMountedNotification])
642 [[NSWorkspace sharedWorkspace] openFile:[clickContext objectForKey:@"path"]];
645 - (IBAction) doSimpleHelp:(id)sender {
646 #pragma unused(sender)
647 [[NSWorkspace sharedWorkspace] openFile:[[NSBundle mainBundle] pathForResource:@"readme" ofType:@"txt"]];