Commits

Peter Hosey  committed 40eb23e

Checking in version 0.1 as published.

  • Participants
  • Tags 0.1

Comments (0)

Files changed (45)

+syntax: glob
+build
+*.swp
+
+*.mode1
+*.mode1v3
+*.mode2
+*.mode2v3
+*.pbxuser
+*.perspective
+*.perspectivev3
+xcuserdata
+
+*~.nib
+
+.DS_Store

File Additions/CFGrowlAdditions.c

+//
+//  CFGrowlAdditions.c
+//  Growl
+//
+//  Created by Mac-arena the Bored Zo on Wed Jun 18 2004.
+//  Copyright 2005 The Growl Project.
+//
+// This file is under the BSD License, refer to License.txt for details
+
+#include <Carbon/Carbon.h>
+#include <c.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include "CFGrowlAdditions.h"
+
+char *createFileSystemRepresentationOfString(CFStringRef str) {
+	char *buffer;
+	if (CFStringGetFileSystemRepresentation) {
+		CFIndex size = CFStringGetMaximumSizeOfFileSystemRepresentation(str);
+		buffer = malloc(size);
+		CFStringGetFileSystemRepresentation(str, buffer, size);
+	} else {
+		buffer = malloc(512);
+		CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, str, kCFURLPOSIXPathStyle, false);
+		if (!CFURLGetFileSystemRepresentation(url, false, (UInt8 *)buffer, 512)) {
+			free(buffer);
+			buffer = NULL;
+		}
+		CFRelease(url);
+	}
+	return buffer;
+}
+
+CFStringRef createStringWithDate(CFDateRef date) {
+	CFLocaleRef locale = CFLocaleCopyCurrent();
+	CFDateFormatterRef dateFormatter = CFDateFormatterCreate(kCFAllocatorDefault,
+															 locale,
+															 kCFDateFormatterMediumStyle,
+															 kCFDateFormatterMediumStyle);
+	CFRelease(locale);
+	CFStringRef dateString = CFDateFormatterCreateStringWithDate(kCFAllocatorDefault,
+																 dateFormatter,
+																 date);
+	CFRelease(dateFormatter);
+	return dateString;
+}
+
+CFStringRef createStringWithContentsOfFile(CFStringRef filename, CFStringEncoding encoding) {
+	CFStringRef str = NULL;
+
+	char *path = createFileSystemRepresentationOfString(filename);
+	if (path) {
+		FILE *fp = fopen(path, "rb");
+		if (fp) {
+			fseek(fp, 0, SEEK_END);
+			unsigned long size = ftell(fp);
+			fseek(fp, 0, SEEK_SET);
+			unsigned char *buffer = malloc(size);
+			if (buffer && fread(buffer, 1, size, fp) == size)
+				str = CFStringCreateWithBytes(kCFAllocatorDefault, buffer, size, encoding, true);
+			fclose(fp);
+		}
+		free(path);
+	}
+
+	return str;
+}
+
+CFStringRef createStringWithStringAndCharacterAndString(CFStringRef str0, UniChar ch, CFStringRef str1) {
+	CFStringRef cfstr0 = (CFStringRef)str0;
+	CFStringRef cfstr1 = (CFStringRef)str1;
+	CFIndex len0 = (cfstr0 ? CFStringGetLength(cfstr0) : 0);
+	CFIndex len1 = (cfstr1 ? CFStringGetLength(cfstr1) : 0);
+	unsigned length = (len0 + (ch != 0xffff) + len1);
+
+	UniChar *buf = malloc(sizeof(UniChar) * length);
+	unsigned i = 0U;
+
+	if (cfstr0) {
+		CFStringGetCharacters(cfstr0, CFRangeMake(0, len0), buf);
+		i += len0;
+	}
+	if (ch != 0xffff)
+		buf[i++] = ch;
+	if (cfstr1)
+		CFStringGetCharacters(cfstr1, CFRangeMake(0, len1), &buf[i]);
+
+	return CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, buf, length, /*contentsDeallocator*/ kCFAllocatorMalloc);
+}
+
+char *copyCString(STRING_TYPE str, CFStringEncoding encoding) {
+	CFIndex size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), encoding) + 1;
+	char *buffer = calloc(size, 1);
+	CFStringGetCString(str, buffer, size, encoding);
+	return buffer;
+}
+
+CFStringRef copyCurrentProcessName(void) {
+	ProcessSerialNumber PSN = { 0, kCurrentProcess };
+	CFStringRef name = NULL;
+	OSStatus err = CopyProcessName(&PSN, &name);
+	if (err != noErr) {
+		NSLog(CFSTR("in copyCurrentProcessName in CFGrowlAdditions: Could not get process name because CopyProcessName returned %li"), (long)err);
+		name = NULL;
+	}
+	return name;
+}
+
+CFURLRef copyCurrentProcessURL(void) {
+	ProcessSerialNumber psn = { 0, kCurrentProcess };
+	FSRef fsref;
+	CFURLRef URL = NULL;
+	OSStatus err = GetProcessBundleLocation(&psn, &fsref);
+	if (err != noErr) {
+		NSLog(CFSTR("in copyCurrentProcessURL in CFGrowlAdditions: Could not get application location, because GetProcessBundleLocation returned %li\n"), (long)err);
+	} else {
+		URL = CFURLCreateFromFSRef(kCFAllocatorDefault, &fsref);
+	}
+	return URL;
+}
+CFStringRef copyCurrentProcessPath(void) {
+	CFURLRef URL = copyCurrentProcessURL();
+	CFStringRef path = CFURLCopyFileSystemPath(URL, kCFURLPOSIXPathStyle);
+	CFRelease(URL);
+	return path;
+}
+
+CFURLRef copyTemporaryFolderURL(void) {
+	FSRef ref;
+	CFURLRef url = NULL;
+
+	OSStatus err = FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType, kCreateFolder, &ref);
+	if (err != noErr)
+		NSLog(CFSTR("in copyTemporaryFolderPath in CFGrowlAdditions: Could not locate temporary folder because FSFindFolder returned %li"), (long)err);
+	else
+		url = CFURLCreateFromFSRef(kCFAllocatorDefault, &ref);
+
+	return url;
+}
+CFStringRef copyTemporaryFolderPath(void) {
+	CFStringRef path = NULL;
+
+	CFURLRef url = copyTemporaryFolderURL();
+	if (url) {
+		path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
+		CFRelease(url);
+	}
+
+	return path;
+}
+
+CFStringRef createStringWithAddressData(CFDataRef aAddressData) {
+	struct sockaddr *socketAddress = (struct sockaddr *)CFDataGetBytePtr(aAddressData);
+	// IPv6 Addresses are "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"
+	//      at max, which is 40 bytes (0-terminated)
+	// IPv4 Addresses are "255.255.255.255" at max which is smaller
+	char stringBuffer[40];
+	CFStringRef addressAsString = NULL;
+	if (socketAddress->sa_family == AF_INET) {
+		struct sockaddr_in *ipv4 = (struct sockaddr_in *)socketAddress;
+		if (inet_ntop(AF_INET, &(ipv4->sin_addr), stringBuffer, 40))
+			addressAsString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s:%d"), stringBuffer, ipv4->sin_port);
+		else
+			addressAsString = CFSTR("IPv4 un-ntopable");
+	} else if (socketAddress->sa_family == AF_INET6) {
+		struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)socketAddress;
+		if (inet_ntop(AF_INET6, &(ipv6->sin6_addr), stringBuffer, 40))
+			// Suggested IPv6 format (see http://www.faqs.org/rfcs/rfc2732.html)
+			addressAsString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("[%s]:%d"), stringBuffer, ipv6->sin6_port);
+		else
+			addressAsString = CFSTR("IPv6 un-ntopable");
+	} else
+		addressAsString = CFSTR("neither IPv6 nor IPv4");
+
+	return addressAsString;
+}
+
+CFStringRef createHostNameForAddressData(CFDataRef aAddressData) {
+	char hostname[NI_MAXHOST];
+	struct sockaddr *socketAddress = (struct sockaddr *)CFDataGetBytePtr(aAddressData);
+	if (getnameinfo(socketAddress, CFDataGetLength(aAddressData),
+					hostname, sizeof(hostname),
+					/*serv*/ NULL, /*servlen*/ 0,
+					NI_NAMEREQD))
+		return NULL;
+	else
+		return CFStringCreateWithCString(kCFAllocatorDefault, hostname, kCFStringEncodingASCII);
+}
+
+CFDataRef copyIconDataForPath(CFStringRef path) {
+	CFDataRef data = NULL;
+
+	//false is probably safest, and is harmless when the object really is a directory.
+	CFURLRef URL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, /*isDirectory*/ false);
+	if (URL) {
+		data = copyIconDataForURL(URL);
+		CFRelease(URL);
+	}
+
+	return data;
+}
+CFDataRef copyIconDataForURL(CFURLRef URL) {
+	CFDataRef data = NULL;
+
+	if (URL) {
+		FSRef ref;
+		if (CFURLGetFSRef(URL, &ref)) {
+			IconRef icon = NULL;
+			SInt16 label_noOneCares;
+			OSStatus err = GetIconRefFromFileInfo(&ref,
+												  /*inFileNameLength*/ 0U, /*inFileName*/ NULL,
+												  kFSCatInfoNone, /*inCatalogInfo*/ NULL,
+												  kIconServicesNoBadgeFlag | kIconServicesUpdateIfNeededFlag,
+												  &icon,
+												  &label_noOneCares);
+			if (err != noErr) {
+				NSLog(CFSTR("in copyIconDataForURL in CFGrowlAdditions: could not get icon for %@: GetIconRefFromFileInfo returned %li\n"), URL, (long)err);
+			} else {
+				IconFamilyHandle fam = NULL;
+				err = IconRefToIconFamily(icon, kSelectorAllAvailableData, &fam);
+				if (err != noErr) {
+					NSLog(CFSTR("in copyIconDataForURL in CFGrowlAdditions: could not get icon for %@: IconRefToIconFamily returned %li\n"), URL, (long)err);
+				} else {
+					HLock((Handle)fam);
+					data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)*(Handle)fam, GetHandleSize((Handle)fam));
+					HUnlock((Handle)fam);
+					DisposeHandle((Handle)fam);
+				}
+				ReleaseIconRef(icon);
+			}
+		}
+	}
+
+	return data;
+}
+
+URL_TYPE createURLByMakingDirectoryAtURLWithName(URL_TYPE parent, STRING_TYPE name) {
+	CFURLRef newDirectory = NULL;
+
+	CFAllocatorRef allocator = parent ? CFGetAllocator(parent) : name ? CFGetAllocator(name) : kCFAllocatorDefault;
+
+	if (parent) parent = CFRetain(parent);
+	else {
+		char *cwdBytes = alloca(PATH_MAX);
+		getcwd(cwdBytes, PATH_MAX);
+		parent = CFURLCreateFromFileSystemRepresentation(allocator, (const unsigned char *)cwdBytes, strlen(cwdBytes), /*isDirectory*/ true);
+		if (!name) {
+			newDirectory = parent;
+			goto end;
+		}
+	}
+	if (!parent)
+		NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: parent directory URL is NULL (please tell the Growl developers)\n"), parent);
+	else {
+		if (name)
+			name = CFRetain(name);
+		else {
+			name = CFURLCopyLastPathComponent(parent);
+			CFURLRef newParent = CFURLCreateCopyDeletingLastPathComponent(allocator, parent);
+			CFRelease(parent);
+			parent = newParent;
+		}
+
+		if (!name)
+			NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: name of directory to create is NULL (please tell the Growl developers)\n"), parent);
+		else {
+			FSRef parentRef;
+			if (!CFURLGetFSRef(parent, &parentRef))
+				NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: could not create FSRef for parent directory at %@ (please tell the Growl developers)\n"), parent);
+			else {
+				FSRef newDirectoryRef;
+
+				struct HFSUniStr255 nameUnicode;
+				CFRange range = { 0, MIN(CFStringGetLength(name), USHRT_MAX) };
+				CFStringGetCharacters(name, range, nameUnicode.unicode);
+				nameUnicode.length = range.length;
+
+				struct FSRefParam refPB = {
+					.ref              = &parentRef,
+					.nameLength       = nameUnicode.length,
+					.name             = nameUnicode.unicode,
+					.whichInfo        = kFSCatInfoNone,
+					.catInfo          = NULL,
+					.textEncodingHint = kTextEncodingUnknown,
+					.newRef           = &newDirectoryRef,
+				};
+
+				OSStatus err = PBCreateDirectoryUnicodeSync(&refPB);
+				if (err == dupFNErr) {
+					//dupFNErr == file (or folder) exists already. this is fine.
+					err = PBMakeFSRefUnicodeSync(&refPB);
+				}
+				if (err == noErr) {
+					NSLog(CFSTR("PBCreateDirectoryUnicodeSync or PBMakeFSRefUnicodeSync returned %li; calling CFURLCreateFromFSRef"), (long)err); //XXX
+					newDirectory = CFURLCreateFromFSRef(allocator, &newDirectoryRef);
+					NSLog(CFSTR("CFURLCreateFromFSRef returned %@"), newDirectory); //XXX
+				} else
+					NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: could not create directory '%@' in parent directory at %@: FSCreateDirectoryUnicode returned %li (please tell the Growl developers)"), name, parent, (long)err);
+			}
+
+			CFRelease(parent);
+		} //if (name)
+		CFRelease(name);
+	} //if (parent)
+
+end:
+	return newDirectory;
+}
+
+#ifndef COPYFORK_BUFSIZE
+#	define COPYFORK_BUFSIZE 5242880U /*5 MiB*/
+#endif
+
+static OSStatus copyFork(const struct HFSUniStr255 *forkName, const FSRef *srcFile, const FSRef *destDir, const struct HFSUniStr255 *destName, FSRef *outDestFile) {
+	OSStatus err, closeErr;
+	struct FSForkIOParam srcPB = {
+		.ref = srcFile,
+		.forkNameLength = forkName->length,
+		.forkName = forkName->unicode,
+		.permissions = fsRdPerm,
+	};
+	unsigned char debuggingPathBuf[PATH_MAX] = "";
+	OSStatus debuggingPathErr;
+
+	err = PBOpenForkSync(&srcPB);
+	if (err != noErr) {
+		debuggingPathErr = FSRefMakePath(srcFile, debuggingPathBuf, PATH_MAX);
+		if (debuggingPathErr != noErr)
+			snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
+		NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBOpenForkSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
+	} else {
+		FSRef destFile;
+
+		/*the first thing to do is get the name of the destination file, if one
+		 *	wasn't provided.
+		 *and while we're at it, we get the catalogue info as well.
+		 */
+		struct FSCatalogInfo catInfo;
+		struct FSRefParam refPB = {
+			.ref       = srcFile,
+			.whichInfo = kFSCatInfoGettableInfo & kFSCatInfoSettableInfo,
+			.catInfo   = &catInfo,
+			.spec      = NULL,
+			.parentRef = NULL,
+			.outName   = destName ? NULL : (struct HFSUniStr255 *)(destName = alloca(sizeof(struct HFSUniStr255))),
+		};
+
+		err = PBGetCatalogInfoSync(&refPB);
+		if (err != noErr) {
+			debuggingPathErr = FSRefMakePath(srcFile, debuggingPathBuf, PATH_MAX);
+			if (debuggingPathErr != noErr)
+				snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
+			NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBGetCatalogInfoSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
+		} else {
+			refPB.ref              = destDir;
+			refPB.nameLength       = destName->length;
+			refPB.name             = destName->unicode;
+			refPB.textEncodingHint = kTextEncodingUnknown;
+			refPB.newRef           = &destFile;
+
+			const char *functionName = "PBMakeFSRefUnicodeSync"; //for error-reporting message
+
+			err = PBMakeFSRefUnicodeSync(&refPB);
+			if ((err != noErr) && (err != fnfErr)) {
+			handleMakeFSRefError:
+				debuggingPathErr = FSRefMakePath(destDir, debuggingPathBuf, PATH_MAX);
+				if (debuggingPathErr != noErr)
+					snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for destination directory: FSRefMakePath returned %li)", (long)debuggingPathErr);
+
+				//get filename too
+				CFStringRef debuggingFilename = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
+																				   destName->unicode,
+																				   destName->length,
+																				   /*contentsDeallocator*/ kCFAllocatorNull);
+				if (!debuggingFilename)
+					debuggingFilename = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, "(could not get filename for destination file: CFStringCreateWithCharactersNoCopy returned NULL)", kCFStringEncodingASCII, /*contentsDeallocator*/ kCFAllocatorNull);
+
+				NSLog(CFSTR("in copyFork in CFGrowlAdditions: %s (destination: %s/%@) returned %li"), functionName, debuggingPathBuf, debuggingFilename, (long)err);
+
+				if (debuggingFilename) CFRelease(debuggingFilename);
+			} else {
+				//that file doesn't exist in that folder; create it.
+				err = PBCreateFileUnicodeSync(&refPB);
+				if (err == noErr) {
+					/*make sure the Finder knows about the new file.
+					 *FNNotify returns a status code too, but this isn't an
+					 *	essential step, so we just ignore it.
+					 */
+					FNNotify(destDir, kFNDirectoryModifiedMessage, kNilOptions);
+				} else if (err == dupFNErr) {
+					/*dupFNErr: the file already exists.
+					 *we can safely ignore this error.
+					 */
+					err = noErr;
+				} else {
+					functionName = "PBCreateFileUnicodeSync";
+					goto handleMakeFSRefError;
+				}
+			}
+		}
+		if (err == noErr) {
+			if (outDestFile)
+				memcpy(outDestFile, &destFile, sizeof(destFile));
+
+			struct FSForkIOParam destPB = {
+				.ref            = &destFile,
+				.forkNameLength = forkName->length,
+				.forkName       = forkName->unicode,
+				.permissions    = fsWrPerm,
+			};
+			err = PBOpenForkSync(&destPB);
+			NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBOpenForkSync (dest) returned %li"), (long)err);
+			if (err != noErr) {
+				debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
+				if (debuggingPathErr != noErr)
+					snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for dest file: FSRefMakePath returned %li)", (long)debuggingPathErr);
+				NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBOpenForkSync (destination: %s) returned %li"), debuggingPathBuf, (long)err);
+			} else {
+				void *buf = malloc(COPYFORK_BUFSIZE);
+				if (buf) {
+					srcPB.buffer = destPB.buffer = buf;
+					srcPB.requestCount = COPYFORK_BUFSIZE;
+					while (err == noErr) {
+						err = PBReadForkSync(&srcPB);
+						if (err == eofErr) {
+							err = noErr;
+							if (srcPB.actualCount == 0)
+								break;
+						}
+						if (err != noErr) {
+							debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
+							if (debuggingPathErr != noErr)
+								snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
+							NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBReadForkSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
+						} else {
+							destPB.requestCount = srcPB.actualCount;
+							err = PBWriteForkSync(&destPB);
+							if (err != noErr) {
+								debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
+								if (debuggingPathErr != noErr)
+									snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for dest file: FSRefMakePath returned %li)", (long)debuggingPathErr);
+								NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBWriteForkSync (destination: %s) returned %li"), debuggingPathBuf, (long)err);
+							}
+						}
+					}
+
+					free(buf);
+				}
+
+				closeErr = PBCloseForkSync(&destPB);
+				if (closeErr != noErr) {
+					debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
+					if (debuggingPathErr != noErr)
+						snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for dest file: FSRefMakePath returned %li)", (long)debuggingPathErr);
+					NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBCloseForkSync (destination: %s) returned %li"), debuggingPathBuf, (long)err);
+				}
+				if (err == noErr) err = closeErr;
+			}
+		}
+
+		closeErr = PBCloseForkSync(&srcPB);
+		if (closeErr != noErr) {
+			debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
+			if (debuggingPathErr != noErr)
+				snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
+			NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBCloseForkSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
+		}
+		if (err == noErr) err = closeErr;
+	}
+
+	return err;
+}
+
+static OSStatus GrowlCopyObjectSync(const FSRef *fileRef, const FSRef *destRef, FSRef *destFileRef) {
+	OSStatus err;
+	struct HFSUniStr255 forkName;
+	struct FSForkIOParam forkPB = {
+		.ref = fileRef,
+		.forkIterator = {
+			.initialize = 0L
+		},
+		.outForkName = &forkName,
+	};
+
+	do {
+		err = PBIterateForksSync(&forkPB);
+		NSLog(CFSTR("PBIterateForksSync returned %li"), (long)err);
+		if (err != noErr) {
+			if (err != errFSNoMoreItems)
+				NSLog(CFSTR("in GrowlCopyObjectSync in CFGrowlAdditions: PBIterateForksSync returned %li"), (long)err);
+		} else {
+			err = copyFork(&forkName, fileRef, destRef, /*destName*/ NULL, /*outDestFile*/ destFileRef);
+			//copyFork prints its own error messages
+		}
+	} while (err == noErr);
+	if (err == errFSNoMoreItems) err = noErr;
+
+	return err;
+}
+
+CFURLRef createURLByCopyingFileFromURLToDirectoryURL(CFURLRef file, CFURLRef dest) {
+	CFURLRef destFileURL = NULL;
+
+	FSRef fileRef, destRef, destFileRef;
+	Boolean gotFileRef = CFURLGetFSRef(file, &fileRef);
+	Boolean gotDestRef = CFURLGetFSRef(dest, &destRef);
+	if (!gotFileRef)
+		NSLog(CFSTR("in createURLByCopyingFileFromURLToDirectoryURL in CFGrowlAdditions: CFURLGetFSRef failed with source URL %@"), file);
+	else if (!gotDestRef)
+		NSLog(CFSTR("in createURLByCopyingFileFromURLToDirectoryURL in CFGrowlAdditions: CFURLGetFSRef failed with destination URL %@"), dest);
+	else {
+		OSStatus err;
+
+		/*
+		 * 10.2 has a problem with weak symbols in frameworks so we use
+		 * MAC_OS_X_VERSION_MIN_REQUIRED >= 10.3.
+		 */
+#if defined(NSAppKitVersionNumber10_3) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
+		if (FSCopyObjectSync) {
+			err = FSCopyObjectSync(&fileRef, &destRef, /*destName*/ NULL, &destFileRef, kFSFileOperationOverwrite);
+		} else {
+#endif
+			err = GrowlCopyObjectSync(&fileRef, &destRef, &destFileRef);
+#if defined(NSAppKitVersionNumber10_3) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
+		}
+#endif
+
+		if (err == noErr)
+			destFileURL = CFURLCreateFromFSRef(kCFAllocatorDefault, &destFileRef);
+		else
+			NSLog(CFSTR("in createURLByCopyingFileFromURLToDirectoryURL in CFGrowlAdditions: CopyObjectSync returned %li for source URL %@"), (long)err, file);
+	}
+
+	return destFileURL;
+}
+
+CFPropertyListRef createPropertyListFromURL(CFURLRef file, u_int32_t mutability, CFPropertyListFormat *outFormat, CFStringRef *outErrorString) {
+	CFPropertyListRef plist = NULL;
+
+	if (!file)
+		NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: cannot read from a NULL URL"));
+	else {
+		CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, file);
+		if (!stream)
+			NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: could not create stream for reading from URL %@"), file);
+		else {
+			if (!CFReadStreamOpen(stream))
+				NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: could not open stream for reading from URL %@"), file);
+			else {
+				CFPropertyListFormat format;
+				CFStringRef errorString = NULL;
+
+				plist = CFPropertyListCreateFromStream(kCFAllocatorDefault,
+													   stream,
+													   /*streamLength*/ 0,
+													   mutability,
+													   &format,
+													   &errorString);
+				if (!plist)
+					NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: could not read property list from URL %@ (error string: %@)"), file, errorString);
+
+				if (outFormat) *outFormat = format;
+				if (errorString) {
+					if (outErrorString)
+						*outErrorString = errorString;
+					else
+						CFRelease(errorString);
+				}
+
+				CFReadStreamClose(stream);
+			}
+
+			CFRelease(stream);
+		}
+	}
+
+	return plist;
+}

File Additions/CFGrowlAdditions.h

+//
+//  CFGrowlAdditions.h
+//  Growl
+//
+//  Created by Mac-arena the Bored Zo on Wed Jun 18 2004.
+//  Copyright 2005 The Growl Project.
+//
+// This file is under the BSD License, refer to License.txt for details
+
+#ifndef HAVE_CFGROWLADDITIONS_H
+#define HAVE_CFGROWLADDITIONS_H
+
+#include "CFGrowlDefines.h"
+
+//see GrowlApplicationBridge-Carbon.c for rationale of using NSLog.
+extern void NSLog(STRING_TYPE format, ...);
+
+char *createFileSystemRepresentationOfString(STRING_TYPE str);
+STRING_TYPE createStringWithDate(DATE_TYPE date);
+
+STRING_TYPE createStringWithContentsOfFile(STRING_TYPE filename, CFStringEncoding encoding);
+
+//you can leave out any of these three components. to leave out the character, pass 0xffff.
+STRING_TYPE createStringWithStringAndCharacterAndString(STRING_TYPE str0, UniChar ch, STRING_TYPE str1);
+
+char *copyCString(STRING_TYPE str, CFStringEncoding encoding);
+
+STRING_TYPE copyCurrentProcessName(void);
+URL_TYPE    copyCurrentProcessURL(void);
+STRING_TYPE copyCurrentProcessPath(void);
+
+URL_TYPE    copyTemporaryFolderURL(void);
+STRING_TYPE copyTemporaryFolderPath(void);
+
+STRING_TYPE createStringWithAddressData(DATA_TYPE aAddressData);
+STRING_TYPE createHostNameForAddressData(DATA_TYPE aAddressData);
+
+/*	@function	copyIconDataForPath
+ *	@param	path	The POSIX path to the file or folder whose icon you want.
+ *	@result	The icon data, in IconFamily format (same as used in the 'icns' resource and in .icns files). You are responsible for releasing this object.
+ */
+DATA_TYPE copyIconDataForPath(STRING_TYPE path);
+/*	@function	copyIconDataForURL
+ *	@param	URL	The URL to the file or folder whose icon you want.
+ *	@result	The icon data, in IconFamily format (same as used in the 'icns' resource and in .icns files). You are responsible for releasing this object.
+ */
+DATA_TYPE copyIconDataForURL(URL_TYPE URL);
+
+/*	@function	createURLByMakingDirectoryAtURLWithName
+ *	@abstract	Create a directory.
+ *	@discussion	This function has a useful side effect: if you pass
+ *	 <code>NULL</code> for both parameters, this function will act basically as
+ *	 CFURL version of <code>getcwd</code>(3).
+ *
+ *	 Also, for CF clients: the allocator used to create the returned URL will
+ *	 be the allocator for the parent URL, the allocator for the name string, or
+ *	 the default allocator, in that order of preference.
+ *	@param	parent	The directory in which to create the new directory. If this is <code>NULL</code>, the current working directory (as returned by <code>getcwd</code>(3)) will be used.
+ *	@param	name	The name of the directory you want to create. If this is <code>NULL</code>, the directory specified by the URL will be created.
+ *	@result	The URL for the directory if it was successfully created (in which case, you are responsible for releasing this object); else, <code>NULL</code>.
+ */
+URL_TYPE createURLByMakingDirectoryAtURLWithName(URL_TYPE parent, STRING_TYPE name);
+
+/*	@function	createURLByCopyingFileFromURLToDirectoryURL
+ *	@param	file	The file to copy.
+ *	@param	dest	The folder to copy it to.
+ *	@result	The copy. You are responsible for releasing this object.
+ */
+URL_TYPE createURLByCopyingFileFromURLToDirectoryURL(URL_TYPE file, URL_TYPE dest);
+
+/*	@function	createPropertyListFromURL
+ *	@abstract	Reads a property list from the contents of an URL.
+ *	@discussion	Creates a property list from the data at an URL (for example, a
+ *	 file URL), and returns it.
+ *	@param	file	The file to read.
+ *	@param	mutability	A mutability-option constant indicating whether the property list (and possibly its contents) should be mutable.
+ *	@param	outFormat	If the property list is read successfully, this will point to the format of the property list. You may pass NULL if you are not interested in this information. If the property list is not read successfully, the value at this pointer will be left unchanged.
+ *	@param	outErrorString	If an error occurs, this will point to a string (which you are responsible for releasing) describing the error. You may pass NULL if you are not interested in this information. If no error occurs, the value at this pointer will be left unchanged.
+ *	@result	The property list. You are responsible for releasing this object.
+ */
+PLIST_TYPE createPropertyListFromURL(URL_TYPE file, u_int32_t mutability, CFPropertyListFormat *outFormat, STRING_TYPE *outErrorString);
+
+#endif

File Additions/CFGrowlDefines.h

+//
+//  CFURLDefines.h
+//  Growl
+//
+//  Created by Ingmar Stein on Fri Sep 16 2005.
+//  Copyright 2005 The Growl Project. All rights reserved.
+//
+// This file is under the BSD License, refer to License.txt for details
+
+#ifndef HAVE_CFGROWLDEFINES_H
+#define HAVE_CFGROWLDEFINES_H
+
+#ifdef __OBJC__
+#	define DATA_TYPE				NSData *
+#	define DATE_TYPE				NSDate *
+#	define DICTIONARY_TYPE			NSDictionary *
+#	define MUTABLE_DICTIONARY_TYPE	NSMutableDictionary *
+#	define STRING_TYPE				NSString *
+#	define ARRAY_TYPE				NSArray *
+#	define URL_TYPE					NSURL *
+#	define PLIST_TYPE				NSObject *
+#	define OBJECT_TYPE				id
+#	define BOOL_TYPE				BOOL
+#else
+#	include <CoreFoundation/CoreFoundation.h>
+#	define DATA_TYPE				CFDataRef
+#	define DATE_TYPE				CFDateRef
+#	define DICTIONARY_TYPE			CFDictionaryRef
+#	define MUTABLE_DICTIONARY_TYPE	CFMutableDictionaryRef
+#	define STRING_TYPE				CFStringRef
+#	define ARRAY_TYPE				CFArrayRef
+#	define URL_TYPE					CFURLRef
+#	define PLIST_TYPE				CFPropertyListRef
+#	define OBJECT_TYPE				CFTypeRef
+#	define BOOL_TYPE				Boolean
+#endif
+
+#endif

File Additions/CFMutableDictionaryAdditions.c

+//
+//  CFMutableDictionaryAdditions.c
+//  Growl
+//
+//  Created by Ingmar Stein on 29.05.05.
+//  Copyright 2005 The Growl Project. All rights reserved.
+//
+// This file is under the BSD License, refer to License.txt for details
+
+#include "CFMutableDictionaryAdditions.h"
+
+void setObjectForKey(CFMutableDictionaryRef dict, const void *key, CFTypeRef value) {
+	CFDictionarySetValue(dict, key, value);
+}
+
+void setIntegerForKey(CFMutableDictionaryRef dict, const void *key, int value) {
+	CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
+	CFDictionarySetValue(dict, key, num);
+	CFRelease(num);
+}
+
+void setBooleanForKey(CFMutableDictionaryRef dict, const void *key, Boolean value) {
+	CFDictionarySetValue(dict, key, value ? kCFBooleanTrue : kCFBooleanFalse);
+}

File Additions/CFMutableDictionaryAdditions.h

+//
+//  CFMutableDictionaryAdditions.h
+//  Growl
+//
+//  Created by Ingmar Stein on 29.05.05.
+//  Copyright 2005 The Growl Project. All rights reserved.
+//
+// This file is under the BSD License, refer to License.txt for details
+
+#ifndef HAVE_CFMUTABLEDICTIONARYADDITIONS_H
+#define HAVE_CFMUTABLEDICTIONARYADDITIONS_H
+
+#include "CFGrowlDefines.h"
+
+void setObjectForKey(MUTABLE_DICTIONARY_TYPE dict, const void *key, OBJECT_TYPE value);
+void setIntegerForKey(MUTABLE_DICTIONARY_TYPE dict, const void *key, int value);
+void setBooleanForKey(MUTABLE_DICTIONARY_TYPE dict, const void *key, BOOL_TYPE value);
+
+#endif

File Additions/CFURLAdditions.c

+//
+//  CFURLAdditions.c
+//  Growl
+//
+//  Created by Karl Adam on Fri May 28 2004.
+//  Copyright 2004-2005 The Growl Project. All rights reserved.
+//
+// This file is under the BSD License, refer to License.txt for details
+
+#include "CFURLAdditions.h"
+#include "CFGrowlAdditions.h"
+#include "CFMutableDictionaryAdditions.h"
+#include <Carbon/Carbon.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define _CFURLAliasDataKey  CFSTR("_CFURLAliasData")
+#define _CFURLStringKey     CFSTR("_CFURLString")
+#define _CFURLStringTypeKey CFSTR("_CFURLStringType")
+
+//'alias' as in the Alias Manager.
+URL_TYPE createFileURLWithAliasData(DATA_TYPE aliasData) {
+	if (!aliasData) {
+		NSLog(CFSTR("WARNING: createFileURLWithAliasData called with NULL aliasData"));
+		return NULL;
+	}
+
+	CFURLRef url = NULL;
+
+	AliasHandle alias = NULL;
+	OSStatus err = PtrToHand(CFDataGetBytePtr(aliasData), (Handle *)&alias, CFDataGetLength(aliasData));
+	if (err != noErr) {
+		NSLog(CFSTR("in createFileURLWithAliasData: Could not allocate an alias handle from %u bytes of alias data (data follows) because PtrToHand returned %li\n%@"), CFDataGetLength(aliasData), aliasData, (long)err);
+	} else {
+		CFStringRef path = NULL;
+		/*
+		 * FSResolveAlias mounts disk images or network shares to resolve
+		 * aliases, thus we resort to FSCopyAliasInfo.
+		 */
+		err = FSCopyAliasInfo(alias,
+							  /* targetName */ NULL,
+							  /* volumeName */ NULL,
+							  &path,
+							  /* whichInfo */ NULL,
+							  /* info */ NULL);
+		DisposeHandle((Handle)alias);
+		if (err != noErr) {
+			if (err != fnfErr) //ignore file-not-found; it's harmless
+				NSLog(CFSTR("in createFileURLWithAliasData: Could not resolve alias (alias data follows) because FSResolveAlias returned %li - will try path\n%@"), (long)err, aliasData);
+		} else if (path) {
+			url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, true);
+		} else {
+			NSLog(CFSTR("in createFileURLWithAliasData: FSCopyAliasInfo returned a NULL path"));
+		}
+		CFRelease(path);
+	}
+
+	NSLog(CFSTR("got URL %@ with alias"), url);
+	return url;
+}
+
+DATA_TYPE createAliasDataWithURL(URL_TYPE theURL) {
+	//return NULL for non-file: URLs.
+	CFStringRef scheme = CFURLCopyScheme(theURL);
+	CFComparisonResult isFileURL = CFStringCompare(scheme, CFSTR("file"), kCFCompareCaseInsensitive);
+	CFRelease(scheme);
+	if (isFileURL != kCFCompareEqualTo)
+		return NULL;
+
+	CFDataRef aliasData = NULL;
+
+	FSRef fsref;
+	if (CFURLGetFSRef(theURL, &fsref)) {
+		AliasHandle alias = NULL;
+		OSStatus    err   = FSNewAlias(/*fromFile*/ NULL, &fsref, &alias);
+		if (err != noErr) {
+			NSLog(CFSTR("in createAliasDataForURL: FSNewAlias for %@ returned %li"), theURL, (long)err);
+		} else {
+			HLock((Handle)alias);
+
+			aliasData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)*alias, GetHandleSize((Handle)alias));
+
+			HUnlock((Handle)alias);
+			DisposeHandle((Handle)alias);
+		}
+	}
+
+	return aliasData;
+}
+
+//these are the type of external representations used by Dock.app.
+URL_TYPE createFileURLWithDockDescription(DICTIONARY_TYPE dict) {
+	CFURLRef url = NULL;
+
+	CFStringRef path      = CFDictionaryGetValue(dict, _CFURLStringKey);
+	NSLog(CFSTR("creating file URL with dock description %p; path is %@"), dict, path);
+	CFDataRef   aliasData = CFDictionaryGetValue(dict, _CFURLAliasDataKey);
+
+	if (aliasData)
+		url = createFileURLWithAliasData(aliasData);
+
+	if (!url) {
+		if (path) {
+			CFNumberRef pathStyleNum = CFDictionaryGetValue(dict, _CFURLStringTypeKey);
+			CFURLPathStyle pathStyle;
+			if (pathStyleNum)
+				CFNumberGetValue(pathStyleNum, kCFNumberIntType, &pathStyle);
+			else
+				pathStyleNum = kCFURLPOSIXPathStyle;
+
+			char *filename = createFileSystemRepresentationOfString(path);
+			int fd = open(filename, O_RDONLY, 0);
+			free(filename);
+			if (fd != -1) {
+				struct stat sb;
+				fstat(fd, &sb);
+				close(fd);
+				url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, pathStyle, /*isDirectory*/ (sb.st_mode & S_IFDIR));
+			}
+		}
+	}
+
+	return url;
+}
+
+DICTIONARY_TYPE createDockDescriptionWithURL(URL_TYPE theURL) {
+	CFMutableDictionaryRef dict;
+
+	if (!theURL) {
+		NSLog(CFSTR("%@"), CFSTR("in createDockDescriptionWithURL: Cannot copy Dock description for a NULL URL"));
+		return NULL;
+	}
+
+	CFStringRef path     = CFURLCopyFileSystemPath(theURL, kCFURLPOSIXPathStyle);
+	CFDataRef aliasData  = createAliasDataWithURL(theURL);
+
+	if (path || aliasData) {
+		dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+
+		if (path) {
+			CFDictionarySetValue(dict, _CFURLStringKey, path);
+			CFRelease(path);
+
+			CFNumberRef pathStyle = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, kCFURLPOSIXPathStyle);
+			CFDictionarySetValue(dict, _CFURLStringTypeKey, pathStyle);
+			CFRelease(pathStyle);
+		}
+
+		if (aliasData) {
+			CFDictionarySetValue(dict, _CFURLAliasDataKey, aliasData);
+			CFRelease(aliasData);
+		}
+	} else {
+		dict = NULL;
+	}
+
+	return dict;
+}

File Additions/CFURLAdditions.h

+//
+//  CFURLAdditions.h
+//  Growl
+//
+//  Created by Karl Adam on Fri May 28 2004.
+//  Copyright 2004-2005 The Growl Project. All rights reserved.
+//
+// This file is under the BSD License, refer to License.txt for details
+
+#ifndef HAVE_CFURLADDITIONS_H
+#define HAVE_CFURLADDITIONS_H
+
+#include <CoreFoundation/CoreFoundation.h>
+#include "CFGrowlDefines.h"
+
+//'alias' as in the Alias Manager.
+URL_TYPE createFileURLWithAliasData(DATA_TYPE aliasData);
+DATA_TYPE createAliasDataWithURL(URL_TYPE theURL);
+
+//these are the type of external representations used by Dock.app.
+URL_TYPE createFileURLWithDockDescription(DICTIONARY_TYPE dict);
+//createDockDescriptionWithURL returns NULL for non-file: URLs.
+DICTIONARY_TYPE createDockDescriptionWithURL(URL_TYPE theURL);
+
+#endif

File Additions/NSMutableArray+EasyMutation.h

+//
+//  NSMutableArray+EasyMutation.h
+//  BZSoundboard
+//
+//  Created by Mac-arena the Bored Zo on 2006-01-22.
+//  Copyright 2006 Mac-arena the Bored Zo. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface NSMutableArray (EasyMutation)
+
+- (void)setObject:(id)obj atIndex:(unsigned)idx;
+
+@end

File Additions/NSMutableArray+EasyMutation.m

+//
+//  NSMutableArray+EasyMutation.m
+//  BZSoundboard
+//
+//  Created by Mac-arena the Bored Zo on 2006-01-22.
+//  Copyright 2006 Mac-arena the Bored Zo. All rights reserved.
+//
+
+#import "NSMutableArray+EasyMutation.h"
+
+
+@implementation NSMutableArray (EasyMutation)
+
+- (void)setObject:(id)obj atIndex:(unsigned)idx {
+	unsigned count = [self count];
+	if(idx == count)
+		[self addObject:obj];
+	else //<>
+		[self replaceObjectAtIndex:idx withObject:obj];
+}
+
+@end

File Additions/NSURLAdditions.h

+//
+//  NSURLAdditions.h
+//  Growl
+//
+//  Created by Karl Adam on Fri May 28 2004.
+//  Copyright 2004-2005 The Growl Project. All rights reserved.
+//
+// This file is under the BSD License, refer to License.txt for details
+
+#import <Foundation/Foundation.h>
+
+@interface NSURL (GrowlAdditions)
+
+//'alias' as in the Alias Manager.
++ (NSURL *) fileURLWithAliasData:(NSData *)aliasData;
+- (NSData *) aliasData;
+
+//these are the type of external representations used by Dock.app.
++ (NSURL *) fileURLWithDockDescription:(NSDictionary *)dict;
+//-dockDescription returns nil for non-file: URLs.
+- (NSDictionary *) dockDescription;
+
+@end

File Additions/NSURLAdditions.m

+//
+//  NSURLAdditions.m
+//  Growl
+//
+//  Created by Karl Adam on Fri May 28 2004.
+//  Copyright 2004-2005 The Growl Project. All rights reserved.
+//
+// This file is under the BSD License, refer to License.txt for details
+
+#import "NSURLAdditions.h"
+#include "CFGrowlAdditions.h"
+
+#define _CFURLAliasDataKey  CFSTR("_CFURLAliasData")
+#define _CFURLStringKey     CFSTR("_CFURLString")
+#define _CFURLStringTypeKey CFSTR("_CFURLStringType")
+
+static Boolean aliasFilter_acceptFirstMatch(union CInfoPBRec *cpb, Boolean *quitFlag, Ptr myDataPtr);
+
+@implementation NSURL (GrowlAdditions)
+
+//'alias' as in the Alias Manager.
++ (NSURL *) fileURLWithAliasData:(NSData *)aliasData {
+	NSParameterAssert(aliasData != nil);
+
+	NSURL *url = nil;
+
+	AliasHandle alias = NULL;
+	CFDataRef cfData = (CFDataRef)aliasData;
+	OSStatus err = PtrToHand(CFDataGetBytePtr(cfData), (Handle *)&alias, CFDataGetLength(cfData));
+	if (err != noErr) {
+		NSLog(@"in +[NSURL(GrowlAdditions) fileURLWithAliasData:]: Could not allocate an alias handle from %u bytes of alias data (data follows) because PtrToHand returned %li\n%@", [aliasData length], aliasData, (long)err);
+	} else {
+		FSRef match;
+		short numMatches = 1;
+		Boolean needsUpdate;
+		err = FSMatchAlias(/*fromFile*/ NULL,
+						   kARMSearch,
+						   alias,
+						   &numMatches,
+						   &match,
+						   &needsUpdate,
+						   aliasFilter_acceptFirstMatch,
+						   /*refcon*/ NULL);
+		if (err != noErr) {
+			if (err != fnfErr) //ignore file-not-found; it's harmless
+				NSLog(@"in +[NSURL(GrowlAdditions) fileURLWithAliasData:]: Could not resolve alias (alias data follows) because FSResolveAlias returned %li - will try path\n%@", (long)err, aliasData);
+		} else {
+			if (numMatches)
+				url = [(NSURL *)CFURLCreateFromFSRef(kCFAllocatorDefault, &match) autorelease];
+			if (numMatches != 1)
+				NSLog(@"in +[NSURL(GrowlAdditions) fileURLWithAliasData:]: FSMatchAlias returned %hi matches, not 1", numMatches);
+		}
+	}
+
+	return url;
+}
+
+- (NSData *) aliasData {
+	//return nil for non-file: URLs.
+	CFStringRef scheme = CFURLCopyScheme((CFURLRef)self);
+	CFComparisonResult isFileURL = CFStringCompare(scheme, CFSTR("file"), kCFCompareCaseInsensitive);
+	CFRelease(scheme);
+	if (isFileURL != kCFCompareEqualTo)
+		return nil;
+
+	NSData *aliasData = nil;
+
+	FSRef fsref;
+	if (CFURLGetFSRef((CFURLRef)self, &fsref)) {
+		AliasHandle alias = NULL;
+		OSStatus    err   = FSNewAlias(/*fromFile*/ NULL, &fsref, &alias);
+		if (err != noErr) {
+			NSLog(@"in -[NSURL(GrowlAdditions) dockDescription]: FSNewAlias for %@ returned %li", self, (long)err);
+		} else {
+			HLock((Handle)alias);
+
+			aliasData = [(id)CFDataCreate(kCFAllocatorDefault, (const UInt8 *)*alias, GetHandleSize((Handle)alias)) autorelease];
+
+			HUnlock((Handle)alias);
+			DisposeHandle((Handle)alias);
+		}
+	}
+
+	return aliasData;
+}
+
+//these are the type of external representations used by Dock.app.
++ (NSURL *) fileURLWithDockDescription:(NSDictionary *)dict {
+	NSURL *URL = nil;
+
+	CFDictionaryRef cfDict    = (CFDictionaryRef)dict;
+	CFStringRef     path      = CFDictionaryGetValue(cfDict, _CFURLStringKey);
+	CFDataRef       aliasData = CFDictionaryGetValue(cfDict, _CFURLAliasDataKey);
+
+	if (aliasData)
+		URL = [self fileURLWithAliasData:(NSData *)aliasData];
+
+	if (!URL) {
+		if (path) {
+			CFNumberRef pathStyleNum = CFDictionaryGetValue(cfDict, _CFURLStringTypeKey);
+			CFURLPathStyle pathStyle;
+			if (pathStyleNum)
+				CFNumberGetValue(pathStyleNum, kCFNumberIntType, &pathStyle);
+			else
+				pathStyleNum = kCFURLPOSIXPathStyle;
+
+			BOOL isDir = YES;
+			BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:(NSString *)path isDirectory:&isDir];
+
+			if (exists)
+				URL = [(NSURL *)CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, pathStyle, /*isDirectory*/ isDir) autorelease];
+		}
+	}
+
+	return URL;
+}
+
+- (NSDictionary *) dockDescription {
+	CFMutableDictionaryRef dict;
+	CFStringRef path     = CFURLCopyPath((CFURLRef)self);
+	CFDataRef aliasData  = (CFDataRef)[self aliasData];
+
+	if (path || aliasData) {
+		dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+
+		if (path) {
+			CFDictionarySetValue(dict, _CFURLStringKey, path);
+			CFRelease(path);
+			[(NSMutableDictionary *)dict setObject:[NSNumber numberWithInt:kCFURLPOSIXPathStyle] forKey:(NSString *)_CFURLStringTypeKey];
+		}
+
+		if (aliasData)
+			CFDictionarySetValue(dict, _CFURLAliasDataKey, aliasData);
+
+		[(id)dict autorelease];
+	} else {
+		dict = NULL;
+	}
+
+	return (NSDictionary *)dict;
+}
+
+@end
+
+static Boolean aliasFilter_acceptFirstMatch(union CInfoPBRec *cpb, Boolean *quitFlag, Ptr myDataPtr) {
+	*quitFlag = true; //stop after this one
+	return false; //don't discard
+}
+

File AudioDevice.h

+//
+//  AudioDevice.h
+//  BZAudioPipe
+//
+//  Created by Mac-arena the Bored Zo on 2005-11-24.
+//  Copyright 2005 Mac-arena the Bored Zo. All rights reserved.
+//
+
+//a new device has (dis)?appeared.
+extern NSString *AudioDeviceWasAddedNotification;
+extern NSString *AudioDeviceWasRemovedNotification;
+
+//the default (in|out)put device has changed.
+extern NSString *AudioDeviceDefaultInputDeviceChangedNotification;
+extern NSString *AudioDeviceDefaultOutputDeviceChangedNotification;
+
+@interface AudioDevice : NSObject {
+	NSString *name;
+	NSString *UID;
+	AudioDeviceID deviceID;
+
+	UInt32 safetyOffset, numBufferFrames;
+}
+
++ (NSArray *)allDevices;
++ (NSArray *)allInputDevices;
++ (NSArray *)allOutputDevices;
+
++ (AudioDevice *)defaultInputOutputDevice;
++ (AudioDevice *)defaultInputDevice;
++ (AudioDevice *)defaultOutputDevice;
+
+#pragma mark -
+
++ (AudioDeviceID)deviceIDWithUID:(NSString *)UID;
++ (NSString *)UIDWithDeviceID:(AudioDeviceID)deviceID;
+
+#pragma mark -
+
++ deviceWithDeviceID:(AudioDeviceID)newID;
++ deviceWithUID:(NSString *)newUID;
+
+- initWithDeviceID:(AudioDeviceID)newID;
+- initWithUID:(NSString *)newUID;
+
+#pragma mark -
+
+- (AudioDeviceID)deviceID;
+- (NSString *)UID;
+
+- (NSString *)name;
+
+- (UInt32)safetyOffset;
+- (UInt32)numberOfBufferFrames;
+
+#pragma mark -
+
+- (BOOL)supportsInput;
+- (BOOL)supportsOutput;
+
+@end

File AudioDevice.m

+//
+//  AudioDevice.m
+//  BZAudioPipe
+//
+//  Created by Mac-arena the Bored Zo on 2005-11-24.
+//  Copyright 2005 Mac-arena the Bored Zo. All rights reserved.
+//
+
+#import "AudioDevice.h"
+
+NSString *AudioDeviceWasAddedNotification = @"AudioDeviceWasAddedNotification";
+NSString *AudioDeviceWasRemovedNotification = @"AudioDeviceWasRemovedNotification";
+
+static NSMutableArray *allDeviceInstances = nil;
+static NSMutableArray *allInputDevices = nil;
+static NSMutableArray *allOutputDevices = nil;
+
+static NSMutableDictionary *UIDsToDevices = nil;
+static NSMutableDictionary *deviceIDsToDevices = nil;
+
+static AudioDevice *defaultInputOutputDevice = nil;
+static AudioDevice *defaultInputDevice = nil;
+static AudioDevice *defaultOutputDevice = nil;
+
+static OSStatus hardwarePropertyListener_devices(AudioHardwarePropertyID inPropertyID, void *refcon);
+static OSStatus hardwarePropertyListener_defaultInputDevice(AudioHardwarePropertyID inPropertyID, void *refcon);
+static OSStatus hardwarePropertyListener_defaultOutputDevice(AudioHardwarePropertyID inPropertyID, void *refcon);
+
+@implementation AudioDevice
+
++ (void)initalize {
+	//if the internal list of devices changes, we want to know about it.
+	OSStatus err = AudioHardwareAddPropertyListener(kAudioHardwarePropertyDevices, hardwarePropertyListener_devices, /*refcon*/ NULL);
+	if(err != kAudioHardwareNoError)
+		NSLog(@"WARNING: tried to register for changes to the array of audio devices, but an error of type %@ occurred", NSFileTypeForHFSTypeCode(err));
+	err = AudioHardwareAddPropertyListener(kAudioHardwarePropertyDefaultInputDevice, hardwarePropertyListener_defaultInputDevice, /*refcon*/ NULL);
+	if(err != kAudioHardwareNoError)
+		NSLog(@"WARNING: tried to register for changes to the array of audio devices, but an error of type %@ occurred", NSFileTypeForHFSTypeCode(err));
+	err = AudioHardwareAddPropertyListener(kAudioHardwarePropertyDefaultOutputDevice, hardwarePropertyListener_defaultOutputDevice, /*refcon*/ NULL);
+	if(err != kAudioHardwareNoError)
+		NSLog(@"WARNING: tried to register for changes to the array of audio devices, but an error of type %@ occurred", NSFileTypeForHFSTypeCode(err));
+	
+	UIDsToDevices = [[NSMutableDictionary alloc] init];
+	deviceIDsToDevices = [[NSMutableDictionary alloc] init];
+}
+
++ (NSData *)allDeviceIDs {
+	NSMutableData *data = nil;
+
+	OSStatus err;
+	UInt32 size;
+		
+	err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, /*outWritable*/ NULL);
+
+	if(err != kAudioHardwareNoError) {
+		NSLog(@"When trying to get the size of the array of audio devices, an error of type %@ occurred", NSFileTypeForHFSTypeCode(err));
+	} else {
+		data = [NSMutableData dataWithLength:size];
+		AudioDeviceID *deviceIDs = [data mutableBytes];
+
+		if(!deviceIDs) {
+			NSLog(@"When trying to get memory for the array of audio devices, an error of type %@ occurred", NSFileTypeForHFSTypeCode(err));
+		} else {
+			err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, deviceIDs);
+
+			if(err != kAudioHardwareNoError)
+				NSLog(@"When trying to get the array of audio devices, an error of type %@ occurred", NSFileTypeForHFSTypeCode(err));
+		}
+	}
+
+	return data;
+}
+
++ (NSArray *)allDevices {
+	if(!allDeviceInstances) {
+		NSData *data = [self allDeviceIDs];
+		const AudioDeviceID *deviceIDs = [data bytes];
+		UInt32 numDevices = [data length] / sizeof(AudioDeviceID);
+
+		allDeviceInstances = [[NSMutableArray alloc] initWithCapacity:numDevices];
+
+		for(UInt32 deviceIdx = 0U; deviceIdx < numDevices; ++deviceIdx)
+			[allDeviceInstances addObject:[self deviceWithDeviceID:deviceIDs[deviceIdx]]];
+	} //if(!allDeviceInstances)
+
+	return allDeviceInstances;
+}
++ (NSArray *)allInputDevices {
+	if(!allInputDevices) {
+		allInputDevices = [[self allDevices] mutableCopy];
+
+		//filter out devices that do not support input.
+		unsigned idx = 0U, len = [allInputDevices count];
+		while(idx < len) {
+			AudioDevice *device = [allInputDevices objectAtIndex:idx];
+			if([device supportsInput]) //keep it; skip over it
+				++idx;
+			else {
+				//remove it from the input-devices array.
+				[allInputDevices removeObjectAtIndex:idx];
+				--len;
+			}
+		}
+	}
+	return allInputDevices;
+}
++ (NSArray *)allOutputDevices {
+	if(!allOutputDevices) {
+		allOutputDevices = [[self allDevices] mutableCopy];
+
+		//filter out devices that do not support input.
+		unsigned idx = 0U, len = [allOutputDevices count];
+		while(idx < len) {
+			AudioDevice *device = [allOutputDevices objectAtIndex:idx];
+			if([device supportsOutput]) //keep it; skip over it
+				++idx;
+			else {
+				//remove it from the input-devices array.
+				[allOutputDevices removeObjectAtIndex:idx];
+				--len;
+			}
+		}
+	}
+	return allOutputDevices;
+}
+
++ (AudioDevice *)defaultInputOutputDevice {
+	if(!defaultInputOutputDevice)
+		defaultInputOutputDevice = [[self alloc] initWithDeviceID:kAudioDeviceUnknown];
+	return defaultInputOutputDevice;
+}
++ (AudioDevice *)defaultInputDevice {
+	if(!defaultInputDevice) {
+		AudioDeviceID deviceID = kAudioDeviceUnknown;
+		UInt32 size = sizeof(deviceID);
+
+		OSStatus err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice,
+												&size,
+												&deviceID);
+		if(err == noErr) {
+			NSNumber *deviceIDNum = [NSNumber numberWithUnsignedInt:deviceID];
+
+			defaultInputDevice = [deviceIDsToDevices objectForKey:deviceIDNum];
+			if(!defaultInputDevice) {
+				//no existing instance - create a new one.
+				defaultInputDevice = [[self alloc] initWithDeviceID:deviceID];
+
+				[UIDsToDevices      setObject:defaultInputDevice forKey:[defaultOutputDevice UID]];
+				[deviceIDsToDevices setObject:defaultInputDevice forKey:deviceIDNum];
+			}
+		} else //XXX report this
+			;
+	}
+
+	return defaultInputDevice;
+}
++ (AudioDevice *)defaultOutputDevice {
+	if(!defaultOutputDevice) {
+		AudioDeviceID deviceID = kAudioDeviceUnknown;
+		UInt32 size = sizeof(deviceID);
+	
+		OSStatus err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
+												&size,
+												&deviceID);
+		if(err == noErr) {
+			NSNumber *deviceIDNum = [NSNumber numberWithUnsignedInt:deviceID];
+
+			defaultOutputDevice = [deviceIDsToDevices objectForKey:deviceIDNum];
+			if(!defaultOutputDevice) {
+				defaultOutputDevice = [[self alloc] initWithDeviceID:deviceID];
+
+				[UIDsToDevices setObject:defaultOutputDevice forKey:[defaultOutputDevice UID]];
+				[deviceIDsToDevices setObject:defaultOutputDevice forKey:deviceIDNum];
+			}
+		} else //XXX report this
+			;
+	}
+
+	return defaultOutputDevice;
+}
+
+#pragma mark -
+
++ (AudioDeviceID)deviceIDWithUID:(NSString *)UID {
+	AudioDeviceID deviceID = kAudioDeviceUnknown;
+
+	if(UID) {
+		struct AudioValueTranslation translator = {
+			&UID,
+			sizeof(UID),
+			&deviceID,
+			sizeof(deviceID),
+		};
+		UInt32 translatorSize = sizeof(translator);
+
+		OSStatus err = AudioHardwareGetProperty(kAudioHardwarePropertyDeviceForUID, &translatorSize, &translator);
+		if(err != kAudioHardwareNoError) {
+			NSLog(@"Got error %@ while mapping UID %@ to a device ID", NSFileTypeForHFSTypeCode(err), UID);
+			return kAudioDeviceUnknown;
+		}
+	}
+
+	return deviceID;
+}
++ (NSString *)UIDWithDeviceID:(AudioDeviceID)deviceID {
+	NSString *UID = nil;
+	UInt32 size = sizeof(UID);
+	
+	OSStatus err = AudioDeviceGetProperty(deviceID,
+										  /*inChannel*/ 0U,
+										  /*isInput*/ false, //shouldn't matter
+										  kAudioDevicePropertyDeviceUID,
+										  &size,
+										  &UID);
+	if(err != kAudioHardwareNoError) {
+		NSLog(@"Got error %@ while mapping device ID %u to a UID", NSFileTypeForHFSTypeCode(err), deviceID);
+		return nil;
+	}
+
+	return UID;
+}
+
+#pragma mark -
+
++ deviceWithDeviceID:(AudioDeviceID)newID {
+	return [[[self alloc] initWithDeviceID:newID] autorelease];
+}
++ deviceWithUID:(NSString *)newUID {
+	return [[[self alloc] initWithUID:newUID] autorelease];
+}
+
+- initWithDeviceID:(AudioDeviceID)newID {
+	NSNumber *num = [NSNumber numberWithUnsignedInt:newID];
+
+	AudioDevice *inst = [deviceIDsToDevices objectForKey:num];
+	if(inst) {
+		[self release];
+		return inst;
+	}
+
+	//no existing instance; create a new one.
+	if((self = [self init])) {
+		deviceID = newID;
+		//UID and name are obtained lazily
+
+		[deviceIDsToDevices setObject:self forKey:num];
+	}
+	return self;
+}
+- initWithUID:(NSString *)newUID {
+	AudioDevice *inst = [UIDsToDevices objectForKey:newUID];
+	if(inst) {
+		if(!inst->UID)
+			inst->UID = [newUID copy];
+	} else {
+		inst = [self initWithDeviceID:[[self class] deviceIDWithUID:newUID]];
+		if(inst) {
+			inst->UID = [newUID copy];
+
+			[UIDsToDevices setObject:inst forKey:newUID];
+		}
+	}
+	return inst;
+}
+
+- (void)dealloc {
+	[UID release];
+	[name release];
+
+	[super dealloc];
+}
+
+#pragma mark -
+
+- (AudioDeviceID)deviceID {
+	return deviceID;
+}
+- (NSString *)UID {
+	if(deviceID == kAudioDeviceUnknown) //default device
+		return nil;
+	else {
+		if(!UID)
+			UID = [[[self class] UIDWithDeviceID:deviceID] copy];
+		return UID;
+	}
+}
+
+- (NSString *)name {
+	if(!name) {
+		UInt32 size = sizeof(name);
+		OSStatus err = AudioDeviceGetProperty(deviceID,
+											  /*inChannel*/ 0U,
+											  /*isInput*/ 0U, //shouldn't matter
+											  kAudioObjectPropertyName,
+											  &size,
+											  &name);
+		if(err != kAudioHardwareNoError)
+			NSLog(@"Got error %@ while getting name of device ID %u (cached UID %@)", NSFileTypeForHFSTypeCode(err), deviceID, UID);
+	}
+
+	return name;
+}
+
+- (UInt32)safetyOffset {
+	return safetyOffset;
+}
+- (UInt32)numberOfBufferFrames {
+	return numBufferFrames;
+}
+
+#pragma mark -
+
+- (BOOL)supportsInput {
+	OSStatus err;
+	UInt32 size;
+	
+	//check input.
+	err = AudioDeviceGetPropertyInfo(deviceID, 0, /*isInput*/ true, kAudioDevicePropertyStreams, &size, /*outWritable*/ NULL);
+	
+	if (err != kAudioHardwareNoError) {
+		NSLog(@"Got error %@ when trying to get size of audio device's input streams array", NSFileTypeForHFSTypeCode(err));
+		return NO;
+	} else
+		return (size > 0U);
+}
+- (BOOL)supportsOutput {
+	OSStatus err;
+	UInt32 size;
+	
+	//check input.
+	err = AudioDeviceGetPropertyInfo(deviceID, 0, /*isInput*/ false, kAudioDevicePropertyStreams, &size, /*outWritable*/ NULL);
+	
+	if (err != kAudioHardwareNoError) {
+		NSLog(@"Got error %@ when trying to get size of audio device's input streams array", NSFileTypeForHFSTypeCode(err));
+		return NO;
+	} else
+		return (size > 0U);
+}
+
+@end
+
+static OSStatus hardwarePropertyListener_devices(AudioHardwarePropertyID inPropertyID, void *refcon) {
+	if(!allDeviceInstances) {
+		//cool! we don't need to update anything.
+		return kAudioHardwareNoError;
+	}
+
+	NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
+
+	NSData *data = [AudioDevice allDeviceIDs];
+
+	const AudioDeviceID *deviceIDs = [data bytes];
+	unsigned numDevices = [data length] / sizeof(AudioDeviceID);
+
+	NSMutableSet *newDeviceIDs = [NSMutableSet setWithCapacity:numDevices];
+	for(unsigned i = 0U; i < numDevices; ++i) {
+		NSNumber *num = [[NSNumber alloc] initWithUnsignedInt:deviceIDs[i]];
+		[newDeviceIDs addObject:num];
+		[num release];
+	}
+
+	NSArray *oldDeviceIDsArray = [allDeviceInstances valueForKey:@"deviceID"];
+	NSMutableSet *oldDeviceIDs = [NSMutableSet setWithArray:oldDeviceIDsArray];
+
+	NSMutableDictionary *oldDevices = [NSMutableDictionary dictionaryWithObjects:allDeviceInstances
+																		 forKeys:oldDeviceIDsArray];
+
+	//add the devices that really are new (in the new-devices set, not in the old-devices set).
+	{
+		NSMutableSet *devicesToAdd = [newDeviceIDs mutableCopy];
+		[devicesToAdd minusSet:oldDeviceIDs];
+
+		NSEnumerator *deviceIDsEnum = [devicesToAdd objectEnumerator];
+		NSNumber *deviceIDNumber;
+		while((deviceIDNumber = [deviceIDsEnum nextObject])) {
+			AudioDevice *device = [[AudioDevice alloc] initWithDeviceID:[deviceIDNumber unsignedIntValue]];
+			[allDeviceInstances addObject:device];
+			[device release];
+
+			[notificationCenter postNotificationName:AudioDeviceWasAddedNotification object:device];
+		}
+
+		[devicesToAdd release];
+	}
+
+	//remove the devices that really are old (in the old-devices set, not in the new-devices set).
+	{
+		NSMutableSet *devicesToRemove = [oldDeviceIDs mutableCopy];
+		[devicesToRemove minusSet:newDeviceIDs];
+
+		NSEnumerator *deviceIDsEnum = [devicesToRemove objectEnumerator];
+		NSNumber *deviceIDNumber;
+		while((deviceIDNumber = [deviceIDsEnum nextObject])) {
+			AudioDeviceID deviceID = [deviceIDNumber unsignedIntValue];
+
+			unsigned i = 0U, len = [allDeviceInstances count];
+			while(i < len) {
+				AudioDevice *device = [allDeviceInstances objectAtIndex:i];
+				if([device deviceID] == deviceID) {
+					//we have a match. remove it.
+					[device retain];
+
+					[allDeviceInstances removeObjectAtIndex:i];
+					--len;
+
+					[notificationCenter postNotificationName:AudioDeviceWasRemovedNotification object:device];
+					[device release];
+				} else
+					++i;
+			}
+		}
+
+		[devicesToRemove release];
+	}
+
+	return noErr;
+}
+
+static OSStatus hardwarePropertyListener_defaultInputDevice(AudioHardwarePropertyID inPropertyID, void *refcon) {
+	[defaultInputDevice release]; defaultInputDevice = nil;
+	return noErr;
+}
+static OSStatus hardwarePropertyListener_defaultOutputDevice(AudioHardwarePropertyID inPropertyID, void *refcon) {
+	[defaultOutputDevice release]; defaultOutputDevice = nil;
+	return noErr;
+}

File AudioPlayer.h

+//
+//  AudioPlayer.h
+//  AudioPlayer
+//
+//  Created by Mac-arena the Bored Zo on 2005-11-15.
+//  Copyright 2005 Mac-arena the Bored Zo. All rights reserved.
+//
+
+@interface AudioPlayer : NSObject {
+	IBOutlet NSWindow *playerWindow;
+	IBOutlet NSMatrix *moviesMatrix;
+	IBOutlet NSPopUpButton *devicesPopUp;
+
+	int lastSelectedDeviceIndex; //in the pop-up menu (not adjusted for Default item, since it could be that item)
+}
+
+@end

File AudioPlayer.m

+//
+//  AudioPlayer.m
+//  AudioPlayer
+//
+//  Created by Mac-arena the Bored Zo on 2005-11-15.
+//  Copyright 2005 Mac-arena the Bored Zo. All rights reserved.
+//
+
+#import "AudioPlayer.h"
+
+#import "NSURLAdditions.h"
+#import "NSMutableArray+EasyMutation.h"
+
+#import "BZMovieButtonCell.h"
+#import "AudioDevice.h"
+
+#include <c.h>
+#include <errno.h>
+#include <string.h>
+#include <math.h>
+#include <float.h>
+#include <unistd.h> //TEMP
+#import <ExceptionHandling/NSExceptionHandler.h> //TEMP
+
+//#define STORED_PLAYLIST_KEY @"Saved playlist"
+#define LAST_DEVICE_KEY @"UID of last selected output device"
+#define CONTENTS_OF_SOUNDBOARD_KEY @"Rows in soundboard"
+
+//guaranteed invalid URL/path indicating an empty cell in the soundboard
+#define EMPTY_CELL @"//empty_cell"
+
+#import "CommonNotifications.h"
+
+@interface AudioPlayer (PRIVATE)
+
+- (void)fillOutDevicesPopUp;
+
+@end
+
+@interface AudioPlayer (PRIVATE_BindingsAccessors)
+
+//relative to (used by) the pop-up menu.
+- (int)currentDeviceSelectionIndex;
+- (void)setCurrentDeviceSelectionIndex:(int)newIndex;
+
+@end
+
+@implementation AudioPlayer
+
+- init {
+	if (self = [super init]) {
+	}
+	return self;
+}
+
+- (void)awakeFromNib {
+	//populate the devices pop-up with all the devices.
+	[self fillOutDevicesPopUp];
+
+	//if we had a device selected at last quit (which practically means, if there WAS a last quit), re-select it.
+	NSString *lastDeviceUID = [[NSUserDefaults standardUserDefaults] stringForKey:LAST_DEVICE_KEY];
+	if(lastDeviceUID) {
+		int lastSelectedIndex = [devicesPopUp indexOfItemWithRepresentedObject:lastDeviceUID];
+		//make sure the device still exists. if not, stay with the default device (already selected in the nib).
+		if(lastSelectedIndex < 0)
+			lastSelectedIndex = 0;
+
+		//store it in our ivar.
+		[self setCurrentDeviceSelectionIndex:lastSelectedIndex];
+	}
+
+	//IB has our top-left cell highlighted and on. this works around that.
+	BZMovieButtonCell *cell = [moviesMatrix cellAtRow:0 column:0];
+	[cell setHighlighted:NO];
+	[cell setState:NSOffState];
+	
+	//position the window appropriately.
+	if(![playerWindow setFrameUsingName:[playerWindow frameAutosaveName]])
+		[playerWindow center];
+	[playerWindow makeKeyAndOrderFront:nil];
+}
+
+- (void)dealloc {
+
+	[super dealloc];
+}
+
+#pragma mark -
+
+- (QTAudioContextRef)createAudioContextWithDeviceUID:(NSString *)deviceUID {
+	QTAudioContextRef audioContext;
+	OSStatus err = QTAudioContextCreateForAudioDevice(kCFAllocatorDefault, (CFStringRef)deviceUID, /*options*/ NULL, &audioContext);
+	if(err != noErr) {
+		NSPanel *alert = NSGetAlertPanel(@"Could not get default device", @"An error of type %li (%s) occurred: %s", @"OK", nil, nil, err, GetMacOSStatusErrorString(err), GetMacOSStatusCommentString(err));
+		[NSApp beginSheet:alert modalForWindow:playerWindow modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
+	}
+
+	return audioContext;
+}
+
+//this must return a new context every time, because QT doesn't like it when we reuse audio contexts (even if the previous movie has been released).
+- (QTAudioContextRef)createAudioContextForSelectedDevice {
+	NSString *deviceUID = [[devicesPopUp selectedItem] representedObject];
+	//if it's the default device, there is no represented object, so the represented object is nil = NULL, so we will create the context with the default device.
+	//if it's a specific device, there is a represented object, so the represented object (UID) is not nil, so we will create the context with the UID for a device.
+
+	//make an audio context for the selected device.
+	return [self createAudioContextWithDeviceUID:deviceUID];
+}
+- (QTMovie *)movieWithFile:(NSString *)path {
+	QTMovie *movie = nil;
+	if(path) {
+		NSError *error = nil;
+		movie = [[[QTMovie alloc] initWithFile:path error:&error] autorelease];
+		if(error) {
+			NSPanel *alert = NSGetAlertPanel(@"Could not open file", @"The file %@ could not be opened because: %@", @"OK", nil, nil, path, [error localizedFailureReason]);
+			[NSApp beginSheet:alert modalForWindow:playerWindow modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
+		}
+
+		//disable all video tracks.
+		NSArray *videoTracks = [movie tracksOfMediaType:QTMediaTypeVideo];
+		NSEnumerator *videoTracksEnum = [videoTracks objectEnumerator];
+		QTTrack *track;
+		while((track = [videoTracksEnum nextObject]))
+			[track setEnabled:NO];
+	
+		QTAudioContextRef audioContext = [self createAudioContextForSelectedDevice];
+		OSStatus err = SetMovieAudioContext([movie quickTimeMovie], audioContext);
+		if(err != noErr) {
+			NSPanel *alert = NSGetAlertPanel(@"Could not set audio device", @"When trying to set the audio output device for the movie, an error of type %i (%s) occurred: %s", @"OK", nil, nil, err, GetMacOSStatusErrorString(err), GetMacOSStatusCommentString(err));
+			[NSApp beginSheet:alert modalForWindow:playerWindow modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
+
+			QTAudioContextRelease(audioContext);
+		}
+	}
+
+	return movie;
+}
+
+#pragma mark NSApplication delegate conformance
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
+	return YES;
+}
+
+- (void)applicationWillFinishLaunching:(NSNotification *)notification {
+	[moviesMatrix registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
+
+	NSRange range = { 0U, [moviesMatrix numberOfRows] };
+	NSArray *rows = [[NSUserDefaults standardUserDefaults] arrayForKey:CONTENTS_OF_SOUNDBOARD_KEY];
+	if([rows count] > range.length)
+		rows = [rows subarrayWithRange:range];
+
+	//in the loop, we use this range to crop the rows to the proper number of columns.
+	range.length = [moviesMatrix numberOfColumns];
+
+	NSEnumerator *rowsEnum = [rows objectEnumerator];
+	NSArray *row;
+	unsigned rowIndex = 0U;
+	while((row = [rowsEnum nextObject])) {
+		NSEnumerator *rowEnum = [row objectEnumerator];
+		NSObject *obj;
+		unsigned colIndex = 0U;
+		while((obj = [rowEnum nextObject])) {
+			if(![obj isEqual:EMPTY_CELL]) {
+				NSDictionary *desc = (NSDictionary *)obj;
+				NSString *path = [[NSURL fileURLWithDockDescription:desc] path];
+
+				NSError *error = nil;
+				QTMovie *movie = [[QTMovie alloc] initWithFile:path error:&error];
+				if(error) {
+					NSPanel *alert = NSGetAlertPanel(@"Could not open file", @"The file %@ could not be opened because: %@", @"OK", nil, nil, path, [error localizedFailureReason]);
+					[NSApp beginSheet:alert modalForWindow:playerWindow modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; 
+				}
+
+				if(movie) {
+					BZMovieButtonCell *cell = [moviesMatrix cellAtRow:rowIndex column:colIndex];
+					[cell setMovie:movie];
+					[movie release];
+				}
+			}
+
+			if(++colIndex >= range.length)
+				break;
+		}
+
+		++rowIndex;
+	}
+}
+
+- (void)applicationWillTerminate:(NSNotification *)notification {
+	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+	NSArray *rowsFromPrefs = [defaults arrayForKey:CONTENTS_OF_SOUNDBOARD_KEY];
+	unsigned rowsFromPrefsCount = [rowsFromPrefs count];
+
+	NSMutableArray *rows = [NSMutableArray arrayWithCapacity:rowsFromPrefsCount];
+
+	unsigned numberOfColsInMatrix = [moviesMatrix numberOfColumns];
+	unsigned numberOfRowsInMatrix = [moviesMatrix numberOfRows];
+
+	NSRange rowsCrop = { 0U, 0U };
+
+	unsigned rowIndex = 0U;
+	NSMutableArray *row;
+	while(rowIndex < numberOfRowsInMatrix) {
+		if(rowIndex < rowsFromPrefsCount)
+			row = [[rowsFromPrefs objectAtIndex:rowIndex] mutableCopy];
+		else
+			row = [[NSMutableArray alloc] init];
+
+		//we want to crop any empty cells off of the end.
+		NSRange columnsCrop = { 0U, 0U };
+
+		unsigned colIndex = 0U;
+		while(colIndex < numberOfColsInMatrix) {
+			BZMovieButtonCell *cell = [moviesMatrix cellAtRow:rowIndex column:colIndex];
+
+			NSObject *value;
+			QTMovie *movie = [cell movie];
+			if(movie) {
+				//we know the movie has a filename because we create them with filenames, as does BZMovieButtonCell.
+				NSString *path = [movie attributeForKey:QTMovieFileNameAttribute];
+				value = [[NSURL fileURLWithPath:path] dockDescription];
+
+				//start columns crop here.
+				columnsCrop.location = colIndex + 1U;
+			} else {
+				value = EMPTY_CELL;
+			}
+
+			[row setObject:value atIndex:colIndex++];
+		}
+
+		//crop off any empty cells at the end.
+		//this may leave the row empty, which is fine.
+		columnsCrop.length = [row count] - columnsCrop.location;
+		if(columnsCrop.length)
+			[row removeObjectsInRange:columnsCrop];
+
+		++rowIndex;
+		
+		//start rows crop here.
+		if([row count])
+			rowsCrop.location = rowIndex;
+
+		[rows addObject:row];
+		[row release];
+	}
+
+	rowsCrop.length = [rows count] - rowsCrop.location;
+	if(rowsCrop.length)
+		[rows removeObjectsInRange:rowsCrop];
+
+	[defaults setObject:rows forKey:CONTENTS_OF_SOUNDBOARD_KEY];
+}
+
+#if 0
+#pragma mark NSExceptionHandler delegate conformance (TEMP)
+
+- (BOOL)exceptionHandler:(NSExceptionHandler *)sender shouldLogException:(NSException *)exception mask:(unsigned int)aMask {
+	NSLog(@"logging exception: %@", exception);
+	NSMutableArray *symbols = [[[[exception userInfo] objectForKey:NSStackTraceKey] componentsSeparatedByString:@"  "] mutableCopy];
+	
+	[symbols insertObject:@"-p" atIndex:0U];
+	[symbols insertObject:[[NSNumber numberWithInt:getpid()] stringValue] atIndex:1U];
+	
+	NSTask *task = [[NSTask alloc] init];
+	[task setLaunchPath:@"/usr/bin/atos"];
+	[task setArguments:symbols];
+	NSPipe *pipe = [NSPipe pipe];
+	[task setStandardOutput:pipe];
+	
+	[task launch];
+	[task waitUntilExit];
+	
+	NSFileHandle *fh = [pipe fileHandleForReading];
+	NSData *data = [fh readDataToEndOfFile];
+	NSString *stackTrace = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
+	
+	[task release];
+	
+	NSLog(@"got %@ with reason %@; stack trace follows\n%@", [exception name], [exception reason], stackTrace);
+	
+	return NO; //because we just did
+}
+#endif //0
+//END TEMP
+
+#pragma mark Generic panel end handler
+
+- (void)panelDidEnd:(NSPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo {
+	[panel close];
+}
+
+#pragma mark Bindings accessors
+
+//relative to (used by) the pop-up menu.
+- (int)currentDeviceSelectionIndex {
+	return lastSelectedDeviceIndex;
+}
+- (void)setCurrentDeviceSelectionIndex:(int)newIndex {
+	[self willChangeValueForKey:@"currentDeviceSelectionIndex"];
+	lastSelectedDeviceIndex = newIndex;
+	[self  didChangeValueForKey:@"currentDeviceSelectionIndex"];
+
+	//store it in user defaults.
+	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+	NSString *deviceUID = [[devicesPopUp selectedItem] representedObject];
+	if(deviceUID)
+		[defaults setObject:deviceUID forKey:LAST_DEVICE_KEY];
+	else
+		[defaults removeObjectForKey:LAST_DEVICE_KEY];
+
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
+		deviceUID, SELECTED_DEVICE_UID,
+		[AudioDevice deviceWithUID:deviceUID], SELECTED_DEVICE_AUDIODEVICE,
+		nil];
+	[[NSNotificationCenter defaultCenter] postNotificationName:SELECTED_DEVICE_CHANGED_NOTIFICATION
+														object:devicesPopUp
+													  userInfo:userInfo];
+}
+
+#pragma mark End of implementation
+@end
+
+@implementation AudioPlayer (PRIVATE)
+
+- (void)fillOutDevicesPopUp {
+	OSStatus err;
+	UInt32 size;
+
+	err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, /*outWritable*/ NULL);
+
+	if(err != noErr) {
+		NSPanel *alert = NSGetAlertPanel(@"Could not get size of audio devices array", @"When trying to get the size of the array of audio output devices, an error of type %i (%s) occurred: %s", @"OK", nil, nil, err, GetMacOSStatusErrorString(err), GetMacOSStatusCommentString(err));
+		[NSApp beginSheet:alert modalForWindow:playerWindow modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
+	} else {
+
+		AudioDeviceID *deviceIDs = malloc(size);
+
+		if(!deviceIDs) {
+			NSPanel *alert = NSGetAlertPanel(@"Could not allocate audio devices array", @"When trying to get memory for the array of audio output devices, an error of type %i occurred: %s", @"OK", nil, nil, errno, strerror(errno));
+			[NSApp beginSheet:alert modalForWindow:playerWindow modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
+		} else {
+
+			err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, deviceIDs);
+
+			if(err != noErr) {
+				NSPanel *alert = NSGetAlertPanel(@"Could not get audio devices array", @"When trying to get the the array of audio output devices, an error of type %i (%s) occurred: %s", @"OK", nil, nil, err, GetMacOSStatusErrorString(err), GetMacOSStatusCommentString(err));
+				[NSApp beginSheet:alert modalForWindow:playerWindow modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
+			} else {
+				UInt32 numDevices = size / sizeof(AudioDeviceID);
+				for(UInt32 deviceIdx = 0U; deviceIdx < numDevices; ++deviceIdx) {
+					NSString *UID;
+					size = sizeof(UID);
+
+					err = AudioDeviceGetProperty(deviceIDs[deviceIdx], /*inChannel*/ 0U, /*isInput*/ false, kAudioDevicePropertyDeviceUID, &size, &UID);
+
+					if(err != noErr) {
+						NSPanel *alert = NSGetAlertPanel(@"Could not get audio device UID", @"When trying to get the UID (unique identifier) of an audio output device, an error of type %i (%s) occurred: %s", @"OK", nil, nil, err, GetMacOSStatusErrorString(err), GetMacOSStatusCommentString(err));
+						[NSApp beginSheet:alert modalForWindow:playerWindow modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
+					} else {
+						NSString *name;
+						size = sizeof(name);
+
+						err = AudioDeviceGetProperty(deviceIDs[deviceIdx], /*inChannel*/ 0U, /*isInput*/ false, kAudioObjectPropertyName, &size, &name);
+
+						if(err != noErr) {
+							NSPanel *alert = NSGetAlertPanel(@"Could not get audio device name", @"When trying to get the name of an audio output device (with UID %@), an error of type %i (%s) occurred: %s", @"OK", nil, nil, UID, err, GetMacOSStatusErrorString(err), GetMacOSStatusCommentString(err));
+							[NSApp beginSheet:alert modalForWindow:playerWindow modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
+						} else {
+							[devicesPopUp addItemWithTitle:name];
+							NSMenuItem *menuItem = [devicesPopUp lastItem];