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
2 Copyright (c) The Growl Project, 2004-2005
6 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
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.
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.
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"
29 #include "CFGrowlAdditions.h"
33 #include <netinet/in.h>
34 #include <sys/types.h>
35 #include <sys/socket.h>
38 #define NOTIFICATION_NAME CFSTR("Command-Line Growl Notification")
40 #define STRINGIFY(x) STRINGIFY2(x)
41 #define STRINGIFY2(x) #x
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"
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"
73 "Display a notification using the title given on the command-line and the\n"
74 "message given in the standard input.\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"
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";
83 static const char *version = "growlnotify 1.1.5\n"
84 "Copyright (c) The Growl Project, 2004-2008";
86 static void notificationDismissed(CFNotificationCenterRef center,
90 CFDictionaryRef userInfo) {
91 #pragma unused(center,observer,name,object,userInfo)
92 CFRunLoopStop(CFRunLoopGetCurrent());
95 static CFDataRef copyIconDataForTypeInfo(CFStringRef typeInfo)
98 CFDataRef data = NULL;
99 OSStatus err = GetIconRefFromTypeInfo(/*inCreator*/ 0,
101 /*inExtension*/ typeInfo,
103 /*inUsageFlags*/kIconServicesNormalUsageFlag,
104 /*outIconRef*/ &icon);
106 IconFamilyHandle fam = NULL;
107 err = IconRefToIconFamily(icon, kSelectorAllAvailableData, &fam);
110 data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)*(Handle)fam, GetHandleSize((Handle)fam));
111 HUnlock((Handle)fam);
112 DisposeHandle((Handle)fam);
114 ReleaseIconRef(icon);
120 int main(int argc, const char **argv) {
127 char *appName = NULL;
128 char *appIcon = NULL;
129 char *iconExt = NULL;
130 char *iconPath = NULL;
131 char *imagePath = NULL;
132 char *message = NULL;
136 BOOL haveProgress = NO;
141 int code = EXIT_SUCCESS;
142 char *password = NULL;
143 char *identifier = NULL;
144 enum GrowlAuthenticationMethod authMethod = GROWL_AUTH_MD5;
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 },
171 while ((ch = getopt_long(argc, (char * const *)argv, "hvn:sa:A:i:I:p:tm:H:uP:d:wc", longopts, NULL)) != -1) {
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) {
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;
220 fprintf(stderr, "Unknown digest algorithm: %s, using default (md5).\n", optarg);
252 port = strdup(optarg);
256 progress = strtod(optarg, NULL);
268 CFMutableStringRef title = CFStringCreateMutable(kCFAllocatorDefault, 0);
271 if (CFStringGetLength(title))
272 CFStringAppend(title, CFSTR(" "));
273 CFStringAppendCString(title, (argv++)[0], kCFStringEncodingUTF8);
277 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
280 // --image takes precedence over -I takes precedence over -i takes precedence over -a
281 CFDataRef icon = NULL;
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);
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];
301 NSURL *appURL = [NSURL fileURLWithPath:appPath];
303 icon = (CFDataRef)copyIconDataForURL(appURL);
309 NSString *appPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"com.apple.Terminal"];
311 NSURL *appURL = [NSURL fileURLWithPath:appPath];
313 icon = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
320 if (message && !(message[0] == '-' && message[1] == 0)) {
322 desc = CFStringCreateWithCString(kCFAllocatorDefault, message, kCFStringEncodingUTF8);
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);
329 CFMutableStringRef temp = CFStringCreateMutable(kCFAllocatorDefault, 0);
330 while (!feof(stdin)) {
331 size_t len = fread(buffer, 1, sizeof(buffer)-1, stdin);
335 CFStringAppendCString(temp, buffer, kCFStringEncodingUTF8);
337 CFStringTrimWhitespace(temp);
342 CFStringRef applicationName;
344 applicationName = CFStringCreateWithCString(kCFAllocatorDefault, appName, kCFStringEncodingUTF8);
346 applicationName = CFSTR("growlnotify");
348 CFStringRef identifierString;
350 identifierString = CFStringCreateWithCString(kCFAllocatorDefault, identifier, kCFStringEncodingUTF8);
352 identifierString = NULL;
354 // Register with Growl
355 CFStringRef name = NOTIFICATION_NAME;
356 CFArrayRef defaultAndAllNotifications = CFArrayCreate(kCFAllocatorDefault, (const void **)&name, 1, &kCFTypeArrayCallBacks);
357 CFTypeRef registerKeys[4] = {
359 GROWL_NOTIFICATIONS_ALL,
360 GROWL_NOTIFICATIONS_DEFAULT,
363 CFTypeRef registerValues[4] = {
365 defaultAndAllNotifications,
366 defaultAndAllNotifications,
369 CFDictionaryRef registerInfo = CFDictionaryCreate(kCFAllocatorDefault, registerKeys, registerValues, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
370 CFRelease(defaultAndAllNotifications);
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);
391 CFRelease(priorityNumber);
392 CFRelease(applicationName);
395 CFRelease(clickContext);
397 CFNumberRef progressNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &progress);
398 CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_PROGRESS, progressNumber);
399 CFRelease(progressNumber);
404 NSLog(@"ERROR: Could not initialize CDSA.");
409 CSSM_DATA registrationPacket;
410 CSSM_DATA notificationPacket;
412 struct addrinfo hints;
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);
421 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error));
424 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
429 registrationPacket.Data = GrowlUDPUtils_registrationToPacket((NSDictionary *)registerInfo,
432 (unsigned *)®istrationPacket.Length);
433 notificationPacket.Data = GrowlUDPUtils_notificationToPacket((NSDictionary *)notificationInfo,
436 (unsigned *)¬ificationPacket.Length);
438 CSSM_DATA passwordData;
439 passwordData.Data = (uint8 *)password;
441 passwordData.Length = strlen(password);
443 passwordData.Length = 0U;
445 GrowlUDPUtils_cryptPacket(®istrationPacket, CSSM_ALGID_AES, &passwordData, YES);
446 GrowlUDPUtils_cryptPacket(¬ificationPacket, CSSM_ALGID_AES, &passwordData, YES);
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");
452 if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&size, sizeof(size)) < 0)
453 perror("setsockopt: SO_BROADCAST");
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) {
462 if (sendto(sock, notificationPacket.Data, notificationPacket.Length, 0, ai->ai_addr, ai->ai_addrlen) < 0) {
466 free(registrationPacket.Data);
467 free(notificationPacket.Data);
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;
477 passwordString = CFStringCreateWithCString(kCFAllocatorDefault, password, kCFStringEncodingUTF8);
479 passwordString = NULL;
481 MD5Authenticator *authenticator = [[MD5Authenticator alloc] initWithPassword:(NSString *)passwordString];
483 CFRelease(passwordString);
484 [connection setDelegate:authenticator];
486 NSDistantObject *theProxy = [connection rootProxy];
487 [theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
488 id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
490 [growlProxy registerApplicationWithDictionary:(NSDictionary *)registerInfo];
491 [growlProxy postNotificationWithDictionary:(NSDictionary *)notificationInfo];
492 } @catch(NSException *e) {
493 if ([[e name] isEqualToString:NSFailedAuthenticationException])
494 NSLog(@"Authentication failed");
496 NSLog(@"Exception: %@", [e name]);
499 [connection release];
500 [authenticator release];
505 CFNotificationCenterRef distCenter = CFNotificationCenterGetDistributedCenter();
507 CFMutableStringRef notificationName = CFStringCreateMutable(kCFAllocatorDefault, 0);
508 CFStringAppend(notificationName, applicationName);
509 CFStringAppend(notificationName, (CFStringRef)GROWL_NOTIFICATION_CLICKED);
510 CFNotificationCenterAddObserver(distCenter,
512 notificationDismissed,
515 CFNotificationSuspensionBehaviorCoalesce);
516 CFStringReplaceAll(notificationName, applicationName);
517 CFStringAppend(notificationName, (CFStringRef)GROWL_NOTIFICATION_TIMED_OUT);
518 CFNotificationCenterAddObserver(distCenter,
520 notificationDismissed,
523 CFNotificationSuspensionBehaviorCoalesce);
524 CFRelease(notificationName);
527 NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
529 //Post to Growl via GrowlApplicationBridgePathway
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);
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);
547 /* Run the run loop until it is manually cancelled in notificationDismissed() */
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);
561 CFRelease(registerInfo);
562 CFRelease(notificationInfo);