Extras/growlnotify/main.m
author Peter Hosey
Sat Feb 28 19:10:16 2009 -0800 (2009-02-28)
changeset 4174 c5aea4e41bce
parent 4145 5f49950abfbb
child 4201 4ec29a49a1cb
permissions -rw-r--r--
Apparently, passing nil to fileURLWithPath: also causes an exception. Fixes a bug reported by “~HP” on the forums: http://forums.cocoaforge.com/viewtopic.php?p=114476#p114476
tick@793
     1
/*
ingmarstein@1275
     2
 Copyright (c) The Growl Project, 2004-2005
tick@793
     3
 All rights reserved.
ingmarstein@1905
     4
ingmarstein@1905
     5
tick@793
     6
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
ingmarstein@1905
     7
ingmarstein@1905
     8
tick@793
     9
 1. Redistributions of source code must retain the above copyright
tick@793
    10
 notice, this list of conditions and the following disclaimer.
tick@793
    11
 2. Redistributions in binary form must reproduce the above copyright
tick@793
    12
 notice, this list of conditions and the following disclaimer in the
tick@793
    13
 documentation and/or other materials provided with the distribution.
tick@793
    14
 3. Neither the name of Growl nor the names of its contributors
tick@793
    15
 may be used to endorse or promote products derived from this software
tick@793
    16
 without specific prior written permission.
ingmarstein@1905
    17
ingmarstein@1905
    18
tick@793
    19
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
ingmarstein@1905
    20
tick@793
    21
*/
tick@727
    22
#import <Foundation/Foundation.h>
tick@727
    23
#import "GrowlDefines.h"
ingmarstein@1118
    24
#import "GrowlDefinesInternal.h"
ingmarstein@1504
    25
#import "GrowlPathway.h"
ingmarstein@1885
    26
#import "MD5Authenticator.h"
ingmarstein@3032
    27
#include "GrowlUDPUtils.h"
ingmarstein@3032
    28
#include "cdsa.h"
ingmarstein@3032
    29
#include "CFGrowlAdditions.h"
ingmarstein@820
    30
ingmarstein@820
    31
#include <unistd.h>
ingmarstein@820
    32
#include <getopt.h>
ingmarstein@820
    33
#include <netinet/in.h>
ingmarstein@820
    34
#include <sys/types.h>
ingmarstein@820
    35
#include <sys/socket.h>
ingmarstein@820
    36
#include <netdb.h>
tick@727
    37
ingmarstein@2650
    38
#define NOTIFICATION_NAME CFSTR("Command-Line Growl Notification")
tick@727
    39
ingmarstein@2370
    40
#define STRINGIFY(x) STRINGIFY2(x)
ingmarstein@2370
    41
#define STRINGIFY2(x) #x
ingmarstein@2370
    42
ingmarstein@1905
    43
static const char usage[] =
ingmarstein@2375
    44
"Usage: growlnotify [-hsvuwc] [-i ext] [-I filepath] [--image filepath]\n"
ingmarstein@2375
    45
"                   [-a appname] [-p priority] [-H host] [-P password]\n"
ingmarstein@2375
    46
"                   [--port port] [-n name] [-A method] [--progress value]\n"
ingmarstein@2391
    47
"                   [--html] [-m message] [-t] [title]\n"
tick@727
    48
"Options:\n"
ingmarstein@1890
    49
"    -h,--help       Display this help\n"
ingmarstein@1890
    50
"    -v,--version    Display version number\n"
ingmarstein@1890
    51
"    -n,--name       Set the name of the application that sends the notification\n"
ingmarstein@1890
    52
"                    [Default: growlnotify]\n"
ingmarstein@2228
    53
"    -s,--sticky     Make the notification sticky\n"
boredzo@4089
    54
"    -a,--appIcon    Specify an application name to take the icon from\n"
aranor@1917
    55
"    -i,--icon       Specify a file type or extension to look up for the\n"
aranor@1917
    56
"                    notification icon\n"
ingmarstein@1890
    57
"    -I,--iconpath   Specify a file whose icon will be the notification icon\n"
aranor@1920
    58
"       --image      Specify an image file to be used for the notification icon\n"
aranor@1920
    59
"    -m,--message    Sets the message to be used instead of using stdin\n"
aranor@1920
    60
"                    Passing - as the argument means read from stdin\n"
ingmarstein@1890
    61
"    -p,--priority   Specify an int or named key (default is 0)\n"
aranor@1920
    62
"    -d,--identifier Specify a notification identifier (used for coalescing)\n"
ingmarstein@1890
    63
"    -H,--host       Specify a hostname to which to send a remote notification.\n"
ingmarstein@1890
    64
"    -P,--password   Password used for remote notifications.\n"
ingmarstein@1890
    65
"    -u,--udp        Use UDP instead of DO to send a remote notification.\n"
aranor@1920
    66
"       --port       Port number for UDP notifications.\n"
ingmarstein@1954
    67
"    -A,--auth       Specify digest algorithm for UDP authentication.\n"
ingmarstein@1954
    68
"                    Either MD5 [Default], SHA256 or NONE.\n"
ingmarstein@1975
    69
"    -c,--crypt      Encrypt UDP notifications.\n"
ingmarstein@1896
    70
"    -w,--wait       Wait until the notification has been dismissed.\n"
ingmarstein@2364
    71
"       --progress   Set a progress value for this notification.\n"
tick@727
    72
"\n"
tick@727
    73
"Display a notification using the title given on the command-line and the\n"
tick@727
    74
"message given in the standard input.\n"
tick@727
    75
"\n"
aranor@1917
    76
"Priority can be one of the following named keys: Very Low, Moderate, Normal,\n"
aranor@1917
    77
"High, Emergency. It can also be an int between -2 and 2.\n"
tick@727
    78
"\n"
aranor@1920
    79
"To be compatible with gNotify the following switch is accepted:\n"
aranor@1920
    80
"    -t,--title      Does nothing. Any text following will be treated as the\n"
aranor@1920
    81
"                    title because that's the default argument behaviour\n";
tick@727
    82
boredzo@4129
    83
static const char *version = "growlnotify 1.1.5\n"
bgannin@4017
    84
"Copyright (c) The Growl Project, 2004-2008";
ingmarstein@1275
    85
ingmarstein@3030
    86
static void notificationDismissed(CFNotificationCenterRef center,
ingmarstein@3030
    87
								  void *observer,
ingmarstein@3030
    88
								  CFStringRef name,
ingmarstein@3030
    89
								  const void *object,
ingmarstein@2650
    90
								  CFDictionaryRef userInfo) {
ingmarstein@2650
    91
#pragma unused(center,observer,name,object,userInfo)
ingmarstein@2650
    92
	CFRunLoopStop(CFRunLoopGetCurrent());
ingmarstein@1896
    93
}
ingmarstein@1896
    94
ingmarstein@3032
    95
static CFDataRef copyIconDataForTypeInfo(CFStringRef typeInfo)
ingmarstein@3032
    96
{
ingmarstein@3032
    97
	IconRef icon;
ingmarstein@3032
    98
	CFDataRef data = NULL;
ingmarstein@3032
    99
	OSStatus err = GetIconRefFromTypeInfo(/*inCreator*/   0,
ingmarstein@3032
   100
										  /*inType*/      0,
ingmarstein@3032
   101
										  /*inExtension*/ typeInfo,
ingmarstein@3032
   102
										  /*inMIMEType*/  NULL,
ingmarstein@3032
   103
										  /*inUsageFlags*/kIconServicesNormalUsageFlag,
ingmarstein@3032
   104
										  /*outIconRef*/  &icon);
ingmarstein@3032
   105
	if (err == noErr) {
ingmarstein@3032
   106
		IconFamilyHandle fam = NULL;
ingmarstein@3032
   107
		err = IconRefToIconFamily(icon, kSelectorAllAvailableData, &fam);
ingmarstein@3032
   108
		if (err == noErr) {
ingmarstein@3032
   109
			HLock((Handle)fam);
ingmarstein@3032
   110
			data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)*(Handle)fam, GetHandleSize((Handle)fam));
ingmarstein@3032
   111
			HUnlock((Handle)fam);
ingmarstein@3032
   112
			DisposeHandle((Handle)fam);
ingmarstein@3032
   113
		}
ingmarstein@3032
   114
		ReleaseIconRef(icon);
ingmarstein@3032
   115
	}
ingmarstein@3032
   116
ingmarstein@3032
   117
	return data;
ingmarstein@3032
   118
}
ingmarstein@3032
   119
tick@727
   120
int main(int argc, const char **argv) {
tick@727
   121
	// options
tick@727
   122
	extern char *optarg;
ingmarstein@3030
   123
	extern int   optind;
ingmarstein@3030
   124
	int          ch;
ingmarstein@3030
   125
	BOOL         isSticky = NO;
ingmarstein@3030
   126
	BOOL         wait = NO;
ingmarstein@3030
   127
	char        *appName = NULL;
ingmarstein@3030
   128
	char        *appIcon = NULL;
ingmarstein@3030
   129
	char        *iconExt = NULL;
ingmarstein@3030
   130
	char        *iconPath = NULL;
ingmarstein@3030
   131
	char        *imagePath = NULL;
ingmarstein@3030
   132
	char        *message = NULL;
ingmarstein@3030
   133
	char        *host = NULL;
ingmarstein@3030
   134
	int          priority = 0;
ingmarstein@3030
   135
	double       progress;
ingmarstein@3030
   136
	BOOL         haveProgress = NO;
ingmarstein@3030
   137
	BOOL         useUDP = NO;
ingmarstein@3030
   138
	BOOL         crypt = NO;
ingmarstein@3030
   139
	int          flag;
ingmarstein@3030
   140
	char        *port = NULL;
ingmarstein@3030
   141
	int          code = EXIT_SUCCESS;
ingmarstein@3030
   142
	char        *password = NULL;
ingmarstein@3030
   143
	char        *identifier = NULL;
ingmarstein@1954
   144
	enum GrowlAuthenticationMethod authMethod = GROWL_AUTH_MD5;
ingmarstein@820
   145
tick@727
   146
	struct option longopts[] = {
ingmarstein@1820
   147
		{ "help",		no_argument,		NULL,	'h' },
ingmarstein@1820
   148
		{ "name",		required_argument,	NULL,	'n' },
ingmarstein@1820
   149
		{ "icon",		required_argument,	NULL,	'i' },
ingmarstein@1820
   150
		{ "iconpath",	required_argument,	NULL,	'I' },
ingmarstein@1820
   151
		{ "appIcon",	required_argument,	NULL,	'a' },
ingmarstein@1820
   152
		{ "image",		required_argument,	&flag,	 1  },
ingmarstein@1820
   153
		{ "title",		no_argument,		NULL,	't' },
ingmarstein@1820
   154
		{ "message",	required_argument,	NULL,	'm' },
ingmarstein@1820
   155
		{ "priority",	required_argument,	NULL,	'p' },
ingmarstein@1820
   156
		{ "host",		required_argument,	NULL,	'H' },
ingmarstein@1820
   157
		{ "udp",		no_argument,		NULL,	'u' },
ingmarstein@1820
   158
		{ "password",	required_argument,	NULL,	'P' },
ingmarstein@1820
   159
		{ "port",		required_argument,	&flag,	 2  },
ingmarstein@1820
   160
		{ "version",	no_argument,		NULL,	'v' },
ingmarstein@1890
   161
		{ "identifier", required_argument,  NULL,   'd' },
ingmarstein@1896
   162
		{ "wait",		no_argument,		NULL,   'w' },
ingmarstein@1954
   163
		{ "auth",		required_argument,	NULL,   'A' },
ingmarstein@1975
   164
		{ "crypt",      no_argument,        NULL,   'c' },
ingmarstein@2228
   165
		{ "sticky",     no_argument,        NULL,   's' },
ingmarstein@2364
   166
		{ "progress",   required_argument,  &flag,   3  },
ingmarstein@2391
   167
		{ "html",       no_argument,        &flag,   4  },
ingmarstein@1820
   168
		{ NULL,			0,					NULL,	 0  }
tick@727
   169
	};
ingmarstein@820
   170
ingmarstein@3028
   171
	while ((ch = getopt_long(argc, (char * const *)argv, "hvn:sa:A:i:I:p:tm:H:uP:d:wc", longopts, NULL)) != -1) {
tick@727
   172
		switch (ch) {
tick@727
   173
		case '?':
ingmarstein@1570
   174
			puts(usage);
ingmarstein@1570
   175
			exit(EXIT_FAILURE);
ingmarstein@1570
   176
			break;
tick@727
   177
		case 'h':
ingmarstein@1570
   178
			puts(usage);
ingmarstein@1570
   179
			exit(EXIT_SUCCESS);
tick@727
   180
			break;
ingmarstein@1275
   181
		case 'v':
ingmarstein@1570
   182
			puts(version);
ingmarstein@1570
   183
			exit(EXIT_SUCCESS);
ingmarstein@1275
   184
			break;
tick@727
   185
		case 'n':
tick@727
   186
			appName = optarg;
tick@727
   187
			break;
tick@727
   188
		case 's':
tick@727
   189
			isSticky = YES;
tick@727
   190
			break;
tick@727
   191
		case 'i':
tick@727
   192
			iconExt = optarg;
tick@727
   193
			break;
tick@727
   194
		case 'I':
tick@727
   195
			iconPath = optarg;
tick@727
   196
			break;
tick@727
   197
		case 'a':
tick@727
   198
			appIcon = optarg;
tick@727
   199
			break;
tick@727
   200
		case 'p':
tick@727
   201
			if (sscanf(optarg, "%d", &priority) == 0) {
tick@727
   202
				// It's not an integer - is it one of the priority keys?
tick@727
   203
				char *keys[] = {"Very Low", "Moderate", "Normal", "High", "Emergency"};
tick@727
   204
				for (int i = 0; i < 5; i++) {
tick@727
   205
					if (strcmp(optarg, keys[i]) == 0) {
tick@727
   206
						priority = i - 2;
tick@727
   207
						break;
tick@727
   208
					}
tick@727
   209
				}
tick@727
   210
			}
tick@727
   211
			break;
ingmarstein@1954
   212
		case 'A':
ingmarstein@2364
   213
			if (!strcasecmp(optarg, "md5"))
ingmarstein@1954
   214
				authMethod = GROWL_AUTH_MD5;
ingmarstein@2364
   215
			else if (!strcasecmp(optarg, "sha256"))
ingmarstein@1954
   216
				authMethod = GROWL_AUTH_SHA256;
ingmarstein@2364
   217
			else if (!strcasecmp(optarg, "none"))
ingmarstein@1954
   218
				authMethod = GROWL_AUTH_NONE;
ingmarstein@2364
   219
			else
ingmarstein@3028
   220
				fprintf(stderr, "Unknown digest algorithm: %s, using default (md5).\n", optarg);
ingmarstein@1954
   221
			break;
tick@727
   222
		case 't':
tick@727
   223
			// do nothing
tick@727
   224
			break;
tick@727
   225
		case 'm':
tick@727
   226
			message = optarg;
tick@727
   227
			break;
ingmarstein@741
   228
		case 'H':
ingmarstein@741
   229
			host = optarg;
ingmarstein@741
   230
			break;
ingmarstein@820
   231
		case 'u':
toby@1623
   232
			useUDP = YES;
ingmarstein@820
   233
			break;
ingmarstein@825
   234
		case 'P':
ingmarstein@825
   235
			password = optarg;
ingmarstein@825
   236
			break;
ingmarstein@1890
   237
		case 'd':
ingmarstein@1890
   238
			identifier = optarg;
ingmarstein@1890
   239
			break;
ingmarstein@1896
   240
		case 'w':
ingmarstein@1896
   241
			wait = YES;
ingmarstein@1896
   242
			break;
ingmarstein@1975
   243
		case 'c':
ingmarstein@1975
   244
			crypt = YES;
ingmarstein@1975
   245
			break;
tick@727
   246
		case 0:
ingmarstein@2364
   247
			switch (flag) {
ingmarstein@2364
   248
				case 1:
ingmarstein@2364
   249
					imagePath = optarg;
ingmarstein@2364
   250
					break;
ingmarstein@2364
   251
				case 2:
ingmarstein@2370
   252
					port = strdup(optarg);
ingmarstein@2364
   253
					break;
ingmarstein@2364
   254
				case 3:
ingmarstein@2364
   255
					haveProgress = YES;
ingmarstein@2364
   256
					progress = strtod(optarg, NULL);
ingmarstein@2364
   257
					break;
ingmarstein@2391
   258
				case 4:
ingmarstein@2391
   259
					break;
tick@727
   260
			}
tick@727
   261
			break;
tick@727
   262
		}
tick@727
   263
	}
tick@727
   264
	argc -= optind;
tick@727
   265
	argv += optind;
ingmarstein@1905
   266
tick@727
   267
	// Deal with title
ingmarstein@2650
   268
	CFMutableStringRef title = CFStringCreateMutable(kCFAllocatorDefault, 0);
tick@727
   269
	while (argc--) {
ingmarstein@2621
   270
		if (strlen(*argv)) {
ingmarstein@2650
   271
			if (CFStringGetLength(title))
ingmarstein@2650
   272
				CFStringAppend(title, CFSTR(" "));
ingmarstein@2650
   273
			CFStringAppendCString(title, (argv++)[0], kCFStringEncodingUTF8);
ingmarstein@2621
   274
		}
tick@727
   275
	}
ingmarstein@1905
   276
ingmarstein@2650
   277
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ingmarstein@3030
   278
tick@727
   279
	// Deal with image
ingmarstein@3032
   280
	// --image takes precedence over -I takes precedence over -i takes precedence over -a
ingmarstein@3031
   281
	CFDataRef icon = NULL;
tick@727
   282
	if (imagePath) {
ingmarstein@3032
   283
		// read the image file into a CFDataRef
ingmarstein@3141
   284
		icon = (CFDataRef)readFile(imagePath);
tick@727
   285
	} else if (iconPath) {
ingmarstein@3032
   286
		// get icon data for path
tick@727
   287
		NSString *path = [[NSString stringWithUTF8String:iconPath] stringByStandardizingPath];
ingmarstein@2650
   288
		if (![path isAbsolutePath])
ingmarstein@3028
   289
			path = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:path];
ingmarstein@3032
   290
		icon = (CFDataRef)copyIconDataForPath(path);
tick@727
   291
	} else if (iconExt) {
ingmarstein@3032
   292
		// get icon data for file extension or type
ingmarstein@2650
   293
		CFStringRef fileType = CFStringCreateWithCString(kCFAllocatorDefault, iconExt, kCFStringEncodingUTF8);
ingmarstein@3032
   294
		icon = copyIconDataForTypeInfo(fileType);
ingmarstein@2650
   295
		CFRelease(fileType);
tick@727
   296
	} else if (appIcon) {
ingmarstein@3032
   297
		// get icon data for application name
ingmarstein@2650
   298
		CFStringRef app = CFStringCreateWithCString(kCFAllocatorDefault, appIcon, kCFStringEncodingUTF8);
Peter@4174
   299
		NSString *appPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:(NSString *)app];
Peter@4174
   300
		if (appPath) {
Peter@4174
   301
			NSURL *appURL = [NSURL fileURLWithPath:appPath];
Peter@4174
   302
			if (appURL) {
Peter@4174
   303
				icon = (CFDataRef)copyIconDataForURL(appURL);
Peter@4174
   304
			}
ingmarstein@3032
   305
		}
ingmarstein@2650
   306
		CFRelease(app);
tick@727
   307
	}
ingmarstein@3032
   308
	if (!icon) {
Peter@4174
   309
		NSString *appPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"com.apple.Terminal"];
Peter@4174
   310
		if (appPath) {
Peter@4174
   311
			NSURL *appURL = [NSURL fileURLWithPath:appPath];
Peter@4174
   312
			if (appURL) {
Peter@4174
   313
				icon = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
Peter@4174
   314
			}
ingmarstein@3032
   315
		}
ingmarstein@3032
   316
	}
ingmarstein@822
   317
tick@727
   318
	// Check message
ingmarstein@2650
   319
	CFStringRef desc;
tick@727
   320
	if (message && !(message[0] == '-' && message[1] == 0)) {
tick@727
   321
		// -m was used
ingmarstein@2650
   322
		desc = CFStringCreateWithCString(kCFAllocatorDefault, message, kCFStringEncodingUTF8);
tick@727
   323
	} else {
tick@727
   324
		// Deal with stdin
boredzo@4145
   325
		if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
boredzo@4145
   326
			fputs("Enter a notification description, followed by newline, followed by Ctrl-D (End of File). To cancel, press Ctrl-C.\n", stdout);
boredzo@4145
   327
ingmarstein@3028
   328
		char buffer[4096];
ingmarstein@3028
   329
		CFMutableStringRef temp = CFStringCreateMutable(kCFAllocatorDefault, 0);
ingmarstein@3028
   330
		while (!feof(stdin)) {
ingmarstein@3028
   331
			size_t len = fread(buffer, 1, sizeof(buffer)-1, stdin);
ingmarstein@3028
   332
			if (!len)
ingmarstein@3028
   333
				break;
ingmarstein@3028
   334
			buffer[len] = '\0';
ingmarstein@3028
   335
			CFStringAppendCString(temp, buffer, kCFStringEncodingUTF8);
ingmarstein@3028
   336
		}
ingmarstein@3028
   337
		CFStringTrimWhitespace(temp);
ingmarstein@3028
   338
		desc = temp;
tick@727
   339
	}
ingmarstein@1905
   340
tick@727
   341
	// Application name
ingmarstein@2650
   342
	CFStringRef applicationName;
ingmarstein@2364
   343
	if (appName)
ingmarstein@2650
   344
		applicationName = CFStringCreateWithCString(kCFAllocatorDefault, appName, kCFStringEncodingUTF8);
ingmarstein@2364
   345
	else
ingmarstein@2650
   346
		applicationName = CFSTR("growlnotify");
ingmarstein@2650
   347
ingmarstein@2650
   348
	CFStringRef identifierString;
ingmarstein@2364
   349
	if (identifier)
ingmarstein@2650
   350
		identifierString = CFStringCreateWithCString(kCFAllocatorDefault, identifier, kCFStringEncodingUTF8);
ingmarstein@2364
   351
	else
ingmarstein@2650
   352
		identifierString = NULL;
ingmarstein@1905
   353
tick@727
   354
	// Register with Growl
ingmarstein@2650
   355
	CFStringRef name = NOTIFICATION_NAME;
ingmarstein@2650
   356
	CFArrayRef defaultAndAllNotifications = CFArrayCreate(kCFAllocatorDefault, (const void **)&name, 1, &kCFTypeArrayCallBacks);
ingmarstein@3028
   357
	CFTypeRef registerKeys[4] = {
ingmarstein@3028
   358
		GROWL_APP_NAME,
ingmarstein@3028
   359
		GROWL_NOTIFICATIONS_ALL,
ingmarstein@3028
   360
		GROWL_NOTIFICATIONS_DEFAULT,
ingmarstein@3028
   361
		GROWL_APP_ICON
ingmarstein@3028
   362
	};
ingmarstein@3028
   363
	CFTypeRef registerValues[4] = {
ingmarstein@3028
   364
		applicationName,
ingmarstein@3028
   365
		defaultAndAllNotifications,
ingmarstein@3028
   366
		defaultAndAllNotifications,
ingmarstein@3028
   367
		icon
ingmarstein@3028
   368
	};
ingmarstein@3028
   369
	CFDictionaryRef registerInfo = CFDictionaryCreate(kCFAllocatorDefault, registerKeys, registerValues, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
ingmarstein@2650
   370
	CFRelease(defaultAndAllNotifications);
ingmarstein@3031
   371
	CFRelease(icon);
ingmarstein@741
   372
tick@727
   373
	// Notify
ingmarstein@3028
   374
	CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
ingmarstein@3028
   375
	CFStringRef clickContext = CFUUIDCreateString(kCFAllocatorDefault, uuid);
ingmarstein@3028
   376
	CFNumberRef priorityNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &priority);
ingmarstein@3017
   377
	CFBooleanRef stickyValue = isSticky ? kCFBooleanTrue : kCFBooleanFalse;
ingmarstein@3028
   378
	CFMutableDictionaryRef notificationInfo = CFDictionaryCreateMutable(kCFAllocatorDefault ,9, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
ingmarstein@3028
   379
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_NAME, name);
ingmarstein@3028
   380
	CFDictionarySetValue(notificationInfo, GROWL_APP_NAME, applicationName);
boredzo@3477
   381
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_TITLE, title);
boredzo@3477
   382
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_DESCRIPTION, desc);
ingmarstein@3028
   383
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_PRIORITY, priorityNumber);
ingmarstein@3028
   384
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_STICKY, stickyValue);
ingmarstein@3028
   385
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_ICON, icon);
ingmarstein@3028
   386
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_CLICK_CONTEXT, clickContext);
ingmarstein@3028
   387
	if (identifierString) {
ingmarstein@3028
   388
		CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_IDENTIFIER, identifierString);
ingmarstein@3028
   389
		CFRelease(identifierString);
ingmarstein@3028
   390
	}
ingmarstein@2650
   391
	CFRelease(priorityNumber);
ingmarstein@2650
   392
	CFRelease(applicationName);
ingmarstein@2650
   393
	CFRelease(title);
ingmarstein@2650
   394
	CFRelease(desc);
ingmarstein@3028
   395
	CFRelease(clickContext);
ingmarstein@2364
   396
	if (haveProgress) {
ingmarstein@2650
   397
		CFNumberRef progressNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &progress);
ingmarstein@3028
   398
		CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_PROGRESS, progressNumber);
ingmarstein@2650
   399
		CFRelease(progressNumber);
ingmarstein@2364
   400
	}
ingmarstein@1890
   401
ingmarstein@1605
   402
	if (host) {
ingmarstein@1971
   403
		if (cdsaInit()) {
ingmarstein@1971
   404
			NSLog(@"ERROR: Could not initialize CDSA.");
ingmarstein@1971
   405
		} else {
ingmarstein@1971
   406
			if (useUDP) {
ingmarstein@3030
   407
				int              sock;
ingmarstein@3030
   408
				unsigned         size;
ingmarstein@3030
   409
				CSSM_DATA        registrationPacket;
ingmarstein@3030
   410
				CSSM_DATA        notificationPacket;
ingmarstein@2370
   411
				struct addrinfo *ai;
ingmarstein@3030
   412
				struct addrinfo  hints;
ingmarstein@3030
   413
				int              error;
ingmarstein@2370
   414
ingmarstein@2370
   415
				memset(&hints, 0, sizeof(hints));
ingmarstein@2370
   416
				hints.ai_family = PF_UNSPEC;
ingmarstein@2370
   417
				hints.ai_socktype = SOCK_DGRAM;
ingmarstein@2370
   418
				hints.ai_protocol = IPPROTO_UDP;
ingmarstein@2370
   419
				error = getaddrinfo(host, port ? port : STRINGIFY(GROWL_UDP_PORT), &hints, &ai);
ingmarstein@2370
   420
				if (error) {
ingmarstein@2370
   421
					fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error));
ingmarstein@820
   422
					code = EXIT_FAILURE;
ingmarstein@820
   423
				} else {
ingmarstein@2370
   424
					sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
ingmarstein@1971
   425
					if (sock == -1) {
ingmarstein@1971
   426
						perror("socket");
ingmarstein@1971
   427
						code = EXIT_FAILURE;
ingmarstein@1971
   428
					} else {
ingmarstein@3028
   429
						registrationPacket.Data = GrowlUDPUtils_registrationToPacket((NSDictionary *)registerInfo,
ingmarstein@2598
   430
																					 authMethod,
ingmarstein@2598
   431
																					 password,
ingmarstein@2598
   432
																					 (unsigned *)&registrationPacket.Length);
ingmarstein@3028
   433
						notificationPacket.Data = GrowlUDPUtils_notificationToPacket((NSDictionary *)notificationInfo,
ingmarstein@2598
   434
																					 authMethod,
ingmarstein@2598
   435
																					 password,
ingmarstein@2598
   436
																					 (unsigned *)&notificationPacket.Length);
ingmarstein@2370
   437
						if (crypt) {
ingmarstein@2370
   438
							CSSM_DATA passwordData;
ingmarstein@2370
   439
							passwordData.Data = (uint8 *)password;
ingmarstein@2370
   440
							if (password)
ingmarstein@2370
   441
								passwordData.Length = strlen(password);
ingmarstein@2370
   442
							else
ingmarstein@2370
   443
								passwordData.Length = 0U;
ingmarstein@2370
   444
ingmarstein@2598
   445
							GrowlUDPUtils_cryptPacket(&registrationPacket, CSSM_ALGID_AES, &passwordData, YES);
ingmarstein@2598
   446
							GrowlUDPUtils_cryptPacket(&notificationPacket, CSSM_ALGID_AES, &passwordData, YES);
ingmarstein@2370
   447
						}
ingmarstein@2370
   448
						size = (registrationPacket.Length > notificationPacket.Length) ? registrationPacket.Length : notificationPacket.Length;
ingmarstein@2370
   449
						if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&size, sizeof(size)) < 0)
ingmarstein@2370
   450
							perror("setsockopt: SO_SNDBUF");
ingmarstein@2984
   451
						size = 1;
ingmarstein@2984
   452
						if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&size, sizeof(size)) < 0)
ingmarstein@2984
   453
							perror("setsockopt: SO_BROADCAST");
ingmarstein@2370
   454
ingmarstein@2370
   455
						//printf( "sendbuf: %d\n", size );
ingmarstein@2370
   456
						//printf( "registration packet length: %d\n", registrationPacket.Length );
ingmarstein@2370
   457
						//printf( "notification packet length: %d\n", notificationPacket.Length );
ingmarstein@2370
   458
						if (sendto(sock, registrationPacket.Data, registrationPacket.Length, 0, ai->ai_addr, ai->ai_addrlen) < 0) {
ingmarstein@2370
   459
							perror("sendto");
ingmarstein@2370
   460
							code = EXIT_FAILURE;
ingmarstein@2370
   461
						}
ingmarstein@2370
   462
						if (sendto(sock, notificationPacket.Data, notificationPacket.Length, 0, ai->ai_addr, ai->ai_addrlen) < 0) {
ingmarstein@2370
   463
							perror("sendto");
ingmarstein@2370
   464
							code = EXIT_FAILURE;
ingmarstein@2370
   465
						}
ingmarstein@2370
   466
						free(registrationPacket.Data);
ingmarstein@2370
   467
						free(notificationPacket.Data);
ingmarstein@2370
   468
						close(sock);
ingmarstein@1971
   469
					}
ingmarstein@2370
   470
					freeaddrinfo(ai);
ingmarstein@820
   471
				}
ingmarstein@1971
   472
			} else {
ingmarstein@1971
   473
				NSSocketPort *port = [[NSSocketPort alloc] initRemoteWithTCPPort:GROWL_TCP_PORT host:[NSString stringWithCString:host]];
ingmarstein@1971
   474
				NSConnection *connection = [[NSConnection alloc] initWithReceivePort:nil sendPort:port];
ingmarstein@2650
   475
				CFStringRef passwordString;
ingmarstein@2370
   476
				if (password)
ingmarstein@2650
   477
					passwordString = CFStringCreateWithCString(kCFAllocatorDefault, password, kCFStringEncodingUTF8);
ingmarstein@2370
   478
				else
ingmarstein@2650
   479
					passwordString = NULL;
ingmarstein@2650
   480
ingmarstein@2650
   481
				MD5Authenticator *authenticator = [[MD5Authenticator alloc] initWithPassword:(NSString *)passwordString];
ingmarstein@2650
   482
				if (passwordString)
ingmarstein@2650
   483
					CFRelease(passwordString);
ingmarstein@1971
   484
				[connection setDelegate:authenticator];
ingmarstein@1971
   485
				@try {
ingmarstein@1971
   486
					NSDistantObject *theProxy = [connection rootProxy];
ingmarstein@1971
   487
					[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
ingmarstein@1971
   488
					id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
ingmarstein@1971
   489
ingmarstein@3028
   490
					[growlProxy registerApplicationWithDictionary:(NSDictionary *)registerInfo];
ingmarstein@3028
   491
					[growlProxy postNotificationWithDictionary:(NSDictionary *)notificationInfo];
ingmarstein@1971
   492
				} @catch(NSException *e) {
ingmarstein@3028
   493
					if ([[e name] isEqualToString:NSFailedAuthenticationException])
ingmarstein@1971
   494
						NSLog(@"Authentication failed");
ingmarstein@3028
   495
					else
ingmarstein@1971
   496
						NSLog(@"Exception: %@", [e name]);
ingmarstein@1971
   497
				} @finally {
ingmarstein@1971
   498
					[port release];
ingmarstein@1971
   499
					[connection release];
ingmarstein@1971
   500
					[authenticator release];
ingmarstein@820
   501
				}
ingmarstein@1885
   502
			}
ingmarstein@820
   503
		}
ingmarstein@741
   504
	} else {
ingmarstein@2650
   505
		CFNotificationCenterRef distCenter = CFNotificationCenterGetDistributedCenter();
ingmarstein@1896
   506
		if (wait) {
ingmarstein@2650
   507
			CFMutableStringRef notificationName = CFStringCreateMutable(kCFAllocatorDefault, 0);
ingmarstein@2650
   508
			CFStringAppend(notificationName, applicationName);
ingmarstein@2650
   509
			CFStringAppend(notificationName, (CFStringRef)GROWL_NOTIFICATION_CLICKED);
ingmarstein@2650
   510
			CFNotificationCenterAddObserver(distCenter,
ingmarstein@2650
   511
											"growlnotify",
ingmarstein@2650
   512
											notificationDismissed,
ingmarstein@2650
   513
											notificationName,
ingmarstein@2650
   514
											/*object*/ NULL,
ingmarstein@2650
   515
											CFNotificationSuspensionBehaviorCoalesce);
ingmarstein@2650
   516
			CFStringReplaceAll(notificationName, applicationName);
ingmarstein@2650
   517
			CFStringAppend(notificationName, (CFStringRef)GROWL_NOTIFICATION_TIMED_OUT);
ingmarstein@2650
   518
			CFNotificationCenterAddObserver(distCenter,
ingmarstein@2650
   519
											"growlnotify",
ingmarstein@2650
   520
											notificationDismissed,
ingmarstein@2650
   521
											notificationName,
ingmarstein@2650
   522
											/*object*/ NULL,
ingmarstein@2650
   523
											CFNotificationSuspensionBehaviorCoalesce);
ingmarstein@2650
   524
			CFRelease(notificationName);
ingmarstein@1971
   525
		}
ingmarstein@1971
   526
ingmarstein@1971
   527
		NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
ingmarstein@1971
   528
		if (connection) {
ingmarstein@1971
   529
			//Post to Growl via GrowlApplicationBridgePathway
ingmarstein@1971
   530
			@try {
ingmarstein@1971
   531
				NSDistantObject *theProxy = [connection rootProxy];
ingmarstein@1971
   532
				[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
ingmarstein@1971
   533
				id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
ingmarstein@3028
   534
				[growlProxy registerApplicationWithDictionary:(NSDictionary *)registerInfo];
ingmarstein@3028
   535
				[growlProxy postNotificationWithDictionary:(NSDictionary *)notificationInfo];
ingmarstein@1971
   536
			} @catch(NSException *e) {
ingmarstein@1971
   537
				NSLog(@"exception while sending notification: %@", e);
ingmarstein@1971
   538
			}
ingmarstein@1971
   539
		} else {
ingmarstein@1971
   540
			//Post to Growl via NSDistributedNotificationCenter
ingmarstein@1971
   541
			NSLog(@"could not find local GrowlApplicationBridgePathway, falling back to NSDNC");
ingmarstein@3028
   542
			CFNotificationCenterPostNotificationWithOptions(distCenter, (CFStringRef)GROWL_APP_REGISTRATION, NULL, registerInfo, kCFNotificationPostToAllSessions);
ingmarstein@3028
   543
			CFNotificationCenterPostNotificationWithOptions(distCenter, (CFStringRef)GROWL_NOTIFICATION, NULL, notificationInfo, kCFNotificationPostToAllSessions);
ingmarstein@1971
   544
		}
ingmarstein@1971
   545
evands@4011
   546
		if (wait) {
evands@4011
   547
			/* Run the run loop until it is manually cancelled in notificationDismissed() */
ingmarstein@2650
   548
			CFRunLoopRun();
evands@4011
   549
		} else {
evands@4011
   550
			/* Run the run loop until we don't have any sources to proces
evands@4011
   551
			 * to ensure the distributed notification is posted */
evands@4011
   552
			while (CFRunLoopRunInMode(/* mode */ kCFRunLoopDefaultMode,
evands@4011
   553
									  /* seconds; 0 means single iteration */ 0,
evands@4011
   554
									  /* returnAfterSourceHandled */ TRUE) == kCFRunLoopRunHandledSource);
evands@4011
   555
		}
ingmarstein@741
   556
	}
ingmarstein@1276
   557
ingmarstein@2370
   558
	if (port)
ingmarstein@2370
   559
		free(port);
ingmarstein@2370
   560
ingmarstein@3028
   561
	CFRelease(registerInfo);
ingmarstein@3028
   562
	CFRelease(notificationInfo);
ingmarstein@3030
   563
	[pool release];
tick@727
   564
ingmarstein@820
   565
	return code;
tick@727
   566
}