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