Extras/growlnotify/main.m
author boredzo
Wed Jul 09 21:35:04 2008 +0000 (2008-07-09)
changeset 4145 5f49950abfbb
parent 4129 3c00479d5f08
child 4174 c5aea4e41bce
permissions -rw-r--r--
Added a prompt message for when stdin and stdout are a tty (i.e., when the user simply types “growlnotify foo” and doesn't specify a description).
     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 		NSURL *appURL = [NSURL fileURLWithPath:[[NSWorkspace sharedWorkspace] fullPathForApplication:(NSString *)app]];
   300 		if (appURL) {
   301 			icon = (CFDataRef)copyIconDataForURL(appURL);
   302 		}
   303 		CFRelease(app);
   304 	}
   305 	if (!icon) {
   306 		NSURL *appURL = [NSURL fileURLWithPath:[[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"com.apple.Terminal"]];
   307 		if (appURL) {
   308 			icon = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
   309 		}
   310 	}
   311 
   312 	// Check message
   313 	CFStringRef desc;
   314 	if (message && !(message[0] == '-' && message[1] == 0)) {
   315 		// -m was used
   316 		desc = CFStringCreateWithCString(kCFAllocatorDefault, message, kCFStringEncodingUTF8);
   317 	} else {
   318 		// Deal with stdin
   319 		if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
   320 			fputs("Enter a notification description, followed by newline, followed by Ctrl-D (End of File). To cancel, press Ctrl-C.\n", stdout);
   321 
   322 		char buffer[4096];
   323 		CFMutableStringRef temp = CFStringCreateMutable(kCFAllocatorDefault, 0);
   324 		while (!feof(stdin)) {
   325 			size_t len = fread(buffer, 1, sizeof(buffer)-1, stdin);
   326 			if (!len)
   327 				break;
   328 			buffer[len] = '\0';
   329 			CFStringAppendCString(temp, buffer, kCFStringEncodingUTF8);
   330 		}
   331 		CFStringTrimWhitespace(temp);
   332 		desc = temp;
   333 	}
   334 
   335 	// Application name
   336 	CFStringRef applicationName;
   337 	if (appName)
   338 		applicationName = CFStringCreateWithCString(kCFAllocatorDefault, appName, kCFStringEncodingUTF8);
   339 	else
   340 		applicationName = CFSTR("growlnotify");
   341 
   342 	CFStringRef identifierString;
   343 	if (identifier)
   344 		identifierString = CFStringCreateWithCString(kCFAllocatorDefault, identifier, kCFStringEncodingUTF8);
   345 	else
   346 		identifierString = NULL;
   347 
   348 	// Register with Growl
   349 	CFStringRef name = NOTIFICATION_NAME;
   350 	CFArrayRef defaultAndAllNotifications = CFArrayCreate(kCFAllocatorDefault, (const void **)&name, 1, &kCFTypeArrayCallBacks);
   351 	CFTypeRef registerKeys[4] = {
   352 		GROWL_APP_NAME,
   353 		GROWL_NOTIFICATIONS_ALL,
   354 		GROWL_NOTIFICATIONS_DEFAULT,
   355 		GROWL_APP_ICON
   356 	};
   357 	CFTypeRef registerValues[4] = {
   358 		applicationName,
   359 		defaultAndAllNotifications,
   360 		defaultAndAllNotifications,
   361 		icon
   362 	};
   363 	CFDictionaryRef registerInfo = CFDictionaryCreate(kCFAllocatorDefault, registerKeys, registerValues, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
   364 	CFRelease(defaultAndAllNotifications);
   365 	CFRelease(icon);
   366 
   367 	// Notify
   368 	CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
   369 	CFStringRef clickContext = CFUUIDCreateString(kCFAllocatorDefault, uuid);
   370 	CFNumberRef priorityNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &priority);
   371 	CFBooleanRef stickyValue = isSticky ? kCFBooleanTrue : kCFBooleanFalse;
   372 	CFMutableDictionaryRef notificationInfo = CFDictionaryCreateMutable(kCFAllocatorDefault ,9, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
   373 	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_NAME, name);
   374 	CFDictionarySetValue(notificationInfo, GROWL_APP_NAME, applicationName);
   375 	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_TITLE, title);
   376 	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_DESCRIPTION, desc);
   377 	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_PRIORITY, priorityNumber);
   378 	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_STICKY, stickyValue);
   379 	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_ICON, icon);
   380 	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_CLICK_CONTEXT, clickContext);
   381 	if (identifierString) {
   382 		CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_IDENTIFIER, identifierString);
   383 		CFRelease(identifierString);
   384 	}
   385 	CFRelease(priorityNumber);
   386 	CFRelease(applicationName);
   387 	CFRelease(title);
   388 	CFRelease(desc);
   389 	CFRelease(clickContext);
   390 	if (haveProgress) {
   391 		CFNumberRef progressNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &progress);
   392 		CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_PROGRESS, progressNumber);
   393 		CFRelease(progressNumber);
   394 	}
   395 
   396 	if (host) {
   397 		if (cdsaInit()) {
   398 			NSLog(@"ERROR: Could not initialize CDSA.");
   399 		} else {
   400 			if (useUDP) {
   401 				int              sock;
   402 				unsigned         size;
   403 				CSSM_DATA        registrationPacket;
   404 				CSSM_DATA        notificationPacket;
   405 				struct addrinfo *ai;
   406 				struct addrinfo  hints;
   407 				int              error;
   408 
   409 				memset(&hints, 0, sizeof(hints));
   410 				hints.ai_family = PF_UNSPEC;
   411 				hints.ai_socktype = SOCK_DGRAM;
   412 				hints.ai_protocol = IPPROTO_UDP;
   413 				error = getaddrinfo(host, port ? port : STRINGIFY(GROWL_UDP_PORT), &hints, &ai);
   414 				if (error) {
   415 					fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error));
   416 					code = EXIT_FAILURE;
   417 				} else {
   418 					sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
   419 					if (sock == -1) {
   420 						perror("socket");
   421 						code = EXIT_FAILURE;
   422 					} else {
   423 						registrationPacket.Data = GrowlUDPUtils_registrationToPacket((NSDictionary *)registerInfo,
   424 																					 authMethod,
   425 																					 password,
   426 																					 (unsigned *)&registrationPacket.Length);
   427 						notificationPacket.Data = GrowlUDPUtils_notificationToPacket((NSDictionary *)notificationInfo,
   428 																					 authMethod,
   429 																					 password,
   430 																					 (unsigned *)&notificationPacket.Length);
   431 						if (crypt) {
   432 							CSSM_DATA passwordData;
   433 							passwordData.Data = (uint8 *)password;
   434 							if (password)
   435 								passwordData.Length = strlen(password);
   436 							else
   437 								passwordData.Length = 0U;
   438 
   439 							GrowlUDPUtils_cryptPacket(&registrationPacket, CSSM_ALGID_AES, &passwordData, YES);
   440 							GrowlUDPUtils_cryptPacket(&notificationPacket, CSSM_ALGID_AES, &passwordData, YES);
   441 						}
   442 						size = (registrationPacket.Length > notificationPacket.Length) ? registrationPacket.Length : notificationPacket.Length;
   443 						if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&size, sizeof(size)) < 0)
   444 							perror("setsockopt: SO_SNDBUF");
   445 						size = 1;
   446 						if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&size, sizeof(size)) < 0)
   447 							perror("setsockopt: SO_BROADCAST");
   448 
   449 						//printf( "sendbuf: %d\n", size );
   450 						//printf( "registration packet length: %d\n", registrationPacket.Length );
   451 						//printf( "notification packet length: %d\n", notificationPacket.Length );
   452 						if (sendto(sock, registrationPacket.Data, registrationPacket.Length, 0, ai->ai_addr, ai->ai_addrlen) < 0) {
   453 							perror("sendto");
   454 							code = EXIT_FAILURE;
   455 						}
   456 						if (sendto(sock, notificationPacket.Data, notificationPacket.Length, 0, ai->ai_addr, ai->ai_addrlen) < 0) {
   457 							perror("sendto");
   458 							code = EXIT_FAILURE;
   459 						}
   460 						free(registrationPacket.Data);
   461 						free(notificationPacket.Data);
   462 						close(sock);
   463 					}
   464 					freeaddrinfo(ai);
   465 				}
   466 			} else {
   467 				NSSocketPort *port = [[NSSocketPort alloc] initRemoteWithTCPPort:GROWL_TCP_PORT host:[NSString stringWithCString:host]];
   468 				NSConnection *connection = [[NSConnection alloc] initWithReceivePort:nil sendPort:port];
   469 				CFStringRef passwordString;
   470 				if (password)
   471 					passwordString = CFStringCreateWithCString(kCFAllocatorDefault, password, kCFStringEncodingUTF8);
   472 				else
   473 					passwordString = NULL;
   474 
   475 				MD5Authenticator *authenticator = [[MD5Authenticator alloc] initWithPassword:(NSString *)passwordString];
   476 				if (passwordString)
   477 					CFRelease(passwordString);
   478 				[connection setDelegate:authenticator];
   479 				@try {
   480 					NSDistantObject *theProxy = [connection rootProxy];
   481 					[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
   482 					id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
   483 
   484 					[growlProxy registerApplicationWithDictionary:(NSDictionary *)registerInfo];
   485 					[growlProxy postNotificationWithDictionary:(NSDictionary *)notificationInfo];
   486 				} @catch(NSException *e) {
   487 					if ([[e name] isEqualToString:NSFailedAuthenticationException])
   488 						NSLog(@"Authentication failed");
   489 					else
   490 						NSLog(@"Exception: %@", [e name]);
   491 				} @finally {
   492 					[port release];
   493 					[connection release];
   494 					[authenticator release];
   495 				}
   496 			}
   497 		}
   498 	} else {
   499 		CFNotificationCenterRef distCenter = CFNotificationCenterGetDistributedCenter();
   500 		if (wait) {
   501 			CFMutableStringRef notificationName = CFStringCreateMutable(kCFAllocatorDefault, 0);
   502 			CFStringAppend(notificationName, applicationName);
   503 			CFStringAppend(notificationName, (CFStringRef)GROWL_NOTIFICATION_CLICKED);
   504 			CFNotificationCenterAddObserver(distCenter,
   505 											"growlnotify",
   506 											notificationDismissed,
   507 											notificationName,
   508 											/*object*/ NULL,
   509 											CFNotificationSuspensionBehaviorCoalesce);
   510 			CFStringReplaceAll(notificationName, applicationName);
   511 			CFStringAppend(notificationName, (CFStringRef)GROWL_NOTIFICATION_TIMED_OUT);
   512 			CFNotificationCenterAddObserver(distCenter,
   513 											"growlnotify",
   514 											notificationDismissed,
   515 											notificationName,
   516 											/*object*/ NULL,
   517 											CFNotificationSuspensionBehaviorCoalesce);
   518 			CFRelease(notificationName);
   519 		}
   520 
   521 		NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
   522 		if (connection) {
   523 			//Post to Growl via GrowlApplicationBridgePathway
   524 			@try {
   525 				NSDistantObject *theProxy = [connection rootProxy];
   526 				[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
   527 				id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
   528 				[growlProxy registerApplicationWithDictionary:(NSDictionary *)registerInfo];
   529 				[growlProxy postNotificationWithDictionary:(NSDictionary *)notificationInfo];
   530 			} @catch(NSException *e) {
   531 				NSLog(@"exception while sending notification: %@", e);
   532 			}
   533 		} else {
   534 			//Post to Growl via NSDistributedNotificationCenter
   535 			NSLog(@"could not find local GrowlApplicationBridgePathway, falling back to NSDNC");
   536 			CFNotificationCenterPostNotificationWithOptions(distCenter, (CFStringRef)GROWL_APP_REGISTRATION, NULL, registerInfo, kCFNotificationPostToAllSessions);
   537 			CFNotificationCenterPostNotificationWithOptions(distCenter, (CFStringRef)GROWL_NOTIFICATION, NULL, notificationInfo, kCFNotificationPostToAllSessions);
   538 		}
   539 
   540 		if (wait) {
   541 			/* Run the run loop until it is manually cancelled in notificationDismissed() */
   542 			CFRunLoopRun();
   543 		} else {
   544 			/* Run the run loop until we don't have any sources to proces
   545 			 * to ensure the distributed notification is posted */
   546 			while (CFRunLoopRunInMode(/* mode */ kCFRunLoopDefaultMode,
   547 									  /* seconds; 0 means single iteration */ 0,
   548 									  /* returnAfterSourceHandled */ TRUE) == kCFRunLoopRunHandledSource);
   549 		}
   550 	}
   551 
   552 	if (port)
   553 		free(port);
   554 
   555 	CFRelease(registerInfo);
   556 	CFRelease(notificationInfo);
   557 	[pool release];
   558 
   559 	return code;
   560 }