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