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