Add loginitemsae source.

Change-Id: I638905b279dfbd3279a87f329563b37f6f45e78b
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff6170f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
+/build
+/source/build
+*.mode1v3
+*.pbxuser
+*.pbxindex/
+!user.pbxuser
+/*.log
+*.user
+*.ncb
+*.suo
+*.pdb
+*.pdf
+*.html
+*.idb
+*.o
+*.lo
+*.a
+*.so
+*.so.0
+*.la
+.deps
+.libs
+*.pyc
+.DS_Store
+# Emacs and other editor backup files
+*~
+
+# builds on Windows
+Debug/
+Release/
+release/
diff --git a/LoginItemsAE.c b/LoginItemsAE.c
new file mode 100644
index 0000000..2d2f986
--- /dev/null
+++ b/LoginItemsAE.c
@@ -0,0 +1,834 @@
+ /*
+	File:		LoginItemsAE.c
+
+	Contains:	Login items manipulation via Apple events.
+
+	Copyright:	Copyright (c) 2005 by Apple Computer, Inc., All Rights Reserved.
+
+	Disclaimer:	IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
+				("Apple") in consideration of your agreement to the following terms, and your
+				use, installation, modification or redistribution of this Apple software
+				constitutes acceptance of these terms.  If you do not agree with these terms,
+				please do not use, install, modify or redistribute this Apple software.
+
+				In consideration of your agreement to abide by the following terms, and subject
+				to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
+				copyrights in this original Apple software (the "Apple Software"), to use,
+				reproduce, modify and redistribute the Apple Software, with or without
+				modifications, in source and/or binary forms; provided that if you redistribute
+				the Apple Software in its entirety and without modifications, you must retain
+				this notice and the following text and disclaimers in all such redistributions of
+				the Apple Software.  Neither the name, trademarks, service marks or logos of
+				Apple Computer, Inc. may be used to endorse or promote products derived from the
+				Apple Software without specific prior written permission from Apple.  Except as
+				expressly stated in this notice, no other rights or licenses, express or implied,
+				are granted by Apple herein, including but not limited to any patent rights that
+				may be infringed by your derivative works or by other works in which the Apple
+				Software may be incorporated.
+
+				The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
+				WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
+				WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+				PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
+				COMBINATION WITH YOUR PRODUCTS.
+
+				IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
+				CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+				GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+				ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
+				OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
+				(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
+				ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+	Change History (most recent first):
+
+$Log: LoginItemsAE.c,v $
+Revision 1.1  2005/09/27 12:29:26         
+First checked in.
+
+
+*/
+
+/////////////////////////////////////////////////////////////////
+
+// Our prototypes
+
+#include "LoginItemsAE.h"
+
+// System interfaces
+
+// We need to pull in all of Carbon just to get the definition of 
+// pProperties.  *sigh*  This is purely a compile-time dependency, 
+// which is why we include it in the implementation and not the 
+// header.
+
+#include <Carbon/Carbon.h>
+
+#include <string.h>
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Apple event utilities
+
+enum {
+	kSystemEventsCreator = 'sevs'
+};
+
+static OSStatus LaunchSystemEvents(ProcessSerialNumber *psnPtr)
+	// Launches the "System Events" process.
+{
+	OSStatus 			err;
+	FSRef				appRef;
+	
+	assert(psnPtr != NULL);
+
+	// Ask Launch Services to find System Events by creator.
+	
+	err = LSFindApplicationForInfo(
+		kSystemEventsCreator,
+		NULL,
+		NULL,
+		&appRef,
+		NULL
+	);
+
+    // Launch it!
+    
+    if (err == noErr) {
+        if ( LSOpenApplication != NULL ) {
+            LSApplicationParameters     appParams;
+            
+            // Do it the easy way on 10.4 and later.
+            
+            memset(&appParams, 0, sizeof(appParams));
+            appParams.version = 0;
+            appParams.flags = kLSLaunchDefaults;
+            appParams.application = &appRef;
+            
+            err = LSOpenApplication(&appParams, psnPtr);
+        } else {
+            FSSpec				appSpec;
+            LaunchParamBlockRec lpb;
+            
+            // Do it the compatible way on earlier systems.
+            
+            // I launch System Events using LaunchApplication, rather than 
+            // Launch Services, because LaunchApplication gives me back 
+            // the ProcessSerialNumber.  Unfortunately this requires me to 
+            // get an FSSpec for the application because there's no 
+            // FSRef version of Launch Application.
+            
+            if (err == noErr) {
+                err = FSGetCatalogInfo(&appRef, kFSCatInfoNone, NULL, NULL, &appSpec, NULL);
+            }
+            if (err == noErr) {
+                memset(&lpb, 0, sizeof(lpb));
+                lpb.launchBlockID      = extendedBlock;
+                lpb.launchEPBLength    = extendedBlockLen;
+                lpb.launchControlFlags = launchContinue | launchNoFileFlags;
+                lpb.launchAppSpec      = &appSpec;
+                
+                err = LaunchApplication(&lpb);
+            }
+            if (err == noErr) {
+                *psnPtr = lpb.launchProcessSN;
+            }
+        }
+    }
+
+	return err;
+}
+
+static OSStatus FindSystemEvents(ProcessSerialNumber *psnPtr)
+	// Finds the "System Events" process or, if it's not 
+	// running, launches it.
+{
+	OSStatus		err;
+	Boolean			found;
+	ProcessInfoRec	info;
+	
+	assert(psnPtr != NULL);
+
+	psnPtr->lowLongOfPSN	= kNoProcess;
+	psnPtr->highLongOfPSN	= kNoProcess;
+
+	do {
+		err = GetNextProcess(psnPtr);
+		if (err == noErr) {	
+			memset(&info, 0, sizeof(info));
+			err = GetProcessInformation(psnPtr, &info);
+		}
+		if (err == noErr) {
+			found = (info.processSignature == kSystemEventsCreator);
+		}
+	} while ( (err == noErr) && ! found );
+
+	if (err == procNotFound) {
+		err = LaunchSystemEvents(psnPtr);
+	}
+	return err;
+}
+
+#if ! defined(LOGIN_ITEMS_AE_PRINT_DESC)
+    #if defined(NDEBUG)
+        #define LOGIN_ITEMS_AE_PRINT_DESC 0
+    #else
+        #define LOGIN_ITEMS_AE_PRINT_DESC 0         // change this to 1 to get output in debug build
+    #endif
+#endif
+
+static OSStatus SendAppleEvent(const AEDesc *event, AEDesc *reply)
+	// This is the bottleneck routine we use for sending Apple events.
+	// It has a number of neato features.
+	// 
+	// o It use the "AEMach.h" routine AESendMessage because that allows 
+	//   us to do an RPC without having to field UI events while waiting 
+	//   for the reply.  Yay for Mac OS X!
+	//
+	// o It automatically extracts the error from the reply.
+	//
+	// o It allows you to enable printing of events and their replies 
+	//   for debugging purposes.
+{
+	static const long kAETimeoutTicks = 5 * 60;
+	OSStatus 	err;
+	OSErr		replyErr;
+	DescType	junkType;
+	Size		junkSize;
+
+	// Normally I don't declare function prototypes in local scope, 
+	// but I made this exception because I don't want anyone except 
+	// for this routine calling GDBPrintAEDesc.  This routine takes 
+	// care to only link with the routine when debugging is enabled; 
+	// everyone else might not be so careful.
+	
+	#if LOGIN_ITEMS_AE_PRINT_DESC
+
+		extern void GDBPrintAEDesc(const AEDesc *desc);
+			// This is private system function used to print a 
+			// textual representation of an AEDesc to stderr.  
+			// It's very handy when debugging, and is meant only 
+			// for that purpose.  It's only available to Mach-O 
+			// clients.  We use it when debugging *only*.
+
+	#endif
+
+	assert(event != NULL);
+	assert(reply != NULL);
+
+	#if LOGIN_ITEMS_AE_PRINT_DESC
+		GDBPrintAEDesc(event);
+	#endif
+
+	err = AESendMessage(event, reply, kAEWaitReply, kAETimeoutTicks);
+
+	#if LOGIN_ITEMS_AE_PRINT_DESC
+		GDBPrintAEDesc(reply);
+	#endif
+
+	// Extract any error from the Apple event handler via the 
+	// keyErrorNumber parameter of the reply.
+	
+	if ( (err == noErr) && (reply->descriptorType != typeNull) ) {
+		err = AEGetParamPtr(
+			reply, 
+			keyErrorNumber, 
+			typeShortInteger, 
+			&junkType,
+			&replyErr, 
+			sizeof(replyErr), 
+			&junkSize
+		);
+		
+		if (err == errAEDescNotFound ) {
+			err = noErr;
+		} else {
+			err = replyErr;
+		}
+	}
+	
+	return err;
+}
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Constants from Login Items AppleScript Dictionary
+
+enum {
+	cLoginItem = 'logi',
+	
+	propPath   = 'ppth',
+	propHidden = 'hidn'
+};
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Public routines (and helpers)
+
+static const AEDesc kAENull = { typeNull, NULL };
+
+static void AEDisposeDescQ(AEDesc *descPtr)
+{
+    OSStatus    junk;
+    
+    junk = AEDisposeDesc(descPtr);
+    assert(junk == noErr);
+    *descPtr = kAENull;
+}
+
+static void CFQRelease(CFTypeRef cf)
+{
+    if (cf != NULL) {
+        CFRelease(cf);
+    }
+}
+
+static OSStatus CreateCFArrayFromAEDescList(
+	const AEDescList *	descList, 
+	CFArrayRef *		itemsPtr
+)
+	// This routine's input is an AEDescList that contains replies 
+	// from the "properties of every login item" event.  Each element 
+	// of the list is an AERecord with two important properties, 
+	// "path" and "hidden".  This routine creates a CFArray that 
+	// corresponds to this list.  Each element of the CFArray 
+	// contains two properties, kLIAEURL and 
+	// kLIAEHidden, that are derived from the corresponding 
+	// AERecord properties.
+	//
+	// On entry, descList must not be NULL
+	// On entry,  itemsPtr must not be NULL
+	// On entry, *itemsPtr must be NULL
+	// On success, *itemsPtr will be a valid CFArray
+	// On error, *itemsPtr will be NULL
+{
+	OSStatus			err;
+	CFMutableArrayRef	result;
+	long				itemCount;
+	long				itemIndex;
+	AEKeyword			junkKeyword;
+	DescType			junkType;
+	Size				junkSize;
+	
+	assert( itemsPtr != NULL);
+	assert(*itemsPtr == NULL);
+
+	result = NULL;
+	
+	// Create a place for the result.
+	
+    err = noErr;
+    result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+    if (result == NULL) {
+        err = coreFoundationUnknownErr;
+    }
+	
+	// For each element in the descriptor list...
+	
+	if (err == noErr) {
+		err = AECountItems(descList, &itemCount);
+	}
+	if (err == noErr) {
+		for (itemIndex = 1; itemIndex <= itemCount; itemIndex++) {
+			AERecord 			thisItem;
+			UInt8 				thisPath[1024];
+			Size				thisPathSize;
+			FSRef				thisItemRef;
+			CFURLRef			thisItemURL;
+			Boolean				thisItemHidden;
+			CFDictionaryRef		thisItemDict;
+			
+			thisItem = kAENull;
+			thisItemURL = NULL;
+			thisItemDict = NULL;
+			
+			// Get this element's AERecord.
+			
+			err = AEGetNthDesc(descList, itemIndex, typeAERecord, &junkKeyword, &thisItem);
+
+			// Extract the path and create a CFURL.
+
+			if (err == noErr) {
+				err = AEGetKeyPtr(
+					&thisItem, 
+					propPath, 
+					typeUTF8Text, 
+					&junkType, 
+					thisPath, 
+					sizeof(thisPath) - 1, 		// to ensure that we can always add null terminator
+					&thisPathSize
+				);
+			}
+			if (err == noErr) {
+				thisPath[thisPathSize] = 0;
+				
+				err = FSPathMakeRef(thisPath, &thisItemRef, NULL);
+				
+				if (err == noErr) {
+					thisItemURL = CFURLCreateFromFSRef(NULL, &thisItemRef);
+				} else {
+					err = noErr;			// swallow error and create an imprecise URL
+					
+					thisItemURL = CFURLCreateFromFileSystemRepresentation(
+						NULL,
+						thisPath,
+						thisPathSize,
+						false
+					);
+				}
+				if (thisItemURL == NULL) {
+					err = coreFoundationUnknownErr;
+				}
+			}
+			
+			// Extract the hidden flag.
+			
+			if (err == noErr) {
+				err = AEGetKeyPtr(
+					&thisItem, 
+					propHidden, 
+					typeBoolean, 
+					&junkType, 
+					&thisItemHidden, 
+					sizeof(thisItemHidden),
+					&junkSize
+				);
+                
+                // Work around <rdar://problem/4052117> by assuming that hidden 
+                // is false if we can't get its value.
+                
+                if (err != noErr) {
+                    thisItemHidden = false;
+                    err = noErr;
+                }
+			}
+
+			// Create the CFDictionary for this item.
+			
+			if (err == noErr) {
+				CFStringRef keys[2];
+				CFTypeRef	values[2];
+				
+				keys[0] = kLIAEURL;
+				keys[1] = kLIAEHidden;
+				
+				values[0] = thisItemURL;
+				values[1] = (thisItemHidden ? kCFBooleanTrue : kCFBooleanFalse);
+
+				thisItemDict = CFDictionaryCreate(
+					NULL,
+					(const void **) keys,
+					values,
+					2,
+					&kCFTypeDictionaryKeyCallBacks,
+					&kCFTypeDictionaryValueCallBacks
+				);
+				if (thisItemDict == NULL) {
+					err = coreFoundationUnknownErr;
+				}
+			}
+			
+			// Add it to the results array.
+			
+			if (err == noErr) {
+				CFArrayAppendValue(result, thisItemDict);
+			}
+						
+			AEDisposeDescQ(&thisItem);
+			CFQRelease(thisItemURL);
+			CFQRelease(thisItemDict);
+						
+			if (err != noErr) {
+				break;
+			}
+		}
+	}
+
+	// Clean up.
+	
+	if (err != noErr) {
+		CFQRelease(result);
+		result = NULL;
+	}
+	*itemsPtr = result;
+	assert( (err == noErr) == (*itemsPtr != NULL) );
+
+	return err;
+}
+
+static OSStatus SendEventToSystemEventsWithParameters(
+	AEEventClass 	theClass,
+	AEEventID    	theEvent,
+	AppleEvent *	reply,
+	...
+)
+	// Creates an Apple event and sends it to the System Events 
+	// process.  theClass and theEvent are the event class and ID, 
+	// respectively.  If reply is not NULL, the caller gets a copy 
+	// of the reply.  Following reply is a variable number of Apple event 
+	// parameters.  Each AE parameter is made up of two C parameters, 
+	// the first being the AEKeyword, the second being a pointer to 
+	// the AEDesc for that parameter.  This list is terminated by an 
+	// AEKeyword of value 0.
+	//
+	// You typically call this as:
+	//
+	// err = SendEventToSystemEventsWithParameters(
+	//     kClass,
+	//     kEvent,
+	//     NULL,
+	//     param1_keyword, param1_desc_ptr, 
+	//     param2_keyword, param2_desc_ptr, 
+	//     0
+	// );
+	//
+	// On entry, reply must be NULL or *reply must be the null AEDesc.
+	// On success, if reply is not NULL, *reply will be the AE reply 
+	// (that is, not a null desc).
+	// On error, if reply is not NULL, *reply will be the null AEDesc.
+{
+	OSStatus			err;
+	ProcessSerialNumber	psn;
+	AppleEvent			target;
+	AppleEvent			event;
+	AppleEvent			localReply;
+	AEDescList			results;
+
+	assert( (reply == NULL) || (reply->descriptorType == typeNull) );
+		
+	target = kAENull;
+	event = kAENull;
+	localReply = kAENull;
+    results = kAENull;
+	
+	// Create Apple event.
+	
+	err = FindSystemEvents(&psn);
+	if (err == noErr) {
+		err = AECreateDesc(typeProcessSerialNumber, &psn, sizeof(psn), &target);
+	}
+	if (err == noErr) {
+		err = AECreateAppleEvent(
+			theClass, 
+			theEvent, 
+			&target, 
+			kAutoGenerateReturnID, 
+			kAnyTransactionID, 
+			&event
+		);
+	}
+
+	// Handle varargs parameters.
+	
+	if (err == noErr) {
+		va_list 		ap;
+		AEKeyword		thisKeyword;
+		const AEDesc *	thisDesc;
+
+		va_start(ap, reply);
+
+		do {
+			thisKeyword = va_arg(ap, AEKeyword);
+			if (thisKeyword != 0) {
+				thisDesc = va_arg(ap, const AEDesc *);
+				assert(thisDesc != NULL);
+				
+				err = AEPutParamDesc(&event, thisKeyword, thisDesc);
+			}
+		} while ( (err == noErr) && (thisKeyword != 0) );
+
+		va_end(ap);
+	}	
+	
+	// Send event and get reply.
+	
+	if (err == noErr) {
+		err = SendAppleEvent(&event, &localReply);
+	}
+	
+	// Clean up.
+	
+	if ( (reply == NULL) || (err != noErr)) {
+		// *reply is already null because of our precondition
+		AEDisposeDescQ(&localReply);
+	} else {
+		*reply = localReply;
+	}
+	AEDisposeDescQ(&event);
+	AEDisposeDescQ(&target);
+	assert( (reply == NULL) || ((err == noErr) == (reply->descriptorType != typeNull)) );
+	
+	return err;
+}
+
+extern OSStatus LIAECopyLoginItems(CFArrayRef *itemsPtr)
+	// See comment in header.
+	//
+	// This routine creates an Apple event that corresponds to the 
+	// AppleScript:
+	//
+	//     get properties of every login item
+	//
+	// and sends it to System Events.  It then processes the reply 
+	// into a CFArray in the format that's documented in the header 
+	// comments.
+{
+	OSStatus			err;
+	AppleEvent			reply;
+	AEDescList			results;
+	AEDesc				propertiesOfEveryLoginItem;
+	
+	assert( itemsPtr != NULL);
+	assert(*itemsPtr == NULL);
+	
+	reply = kAENull;
+	results = kAENull;
+	propertiesOfEveryLoginItem = kAENull;
+	
+	// Build object specifier for "properties of every login item".
+
+	{
+		static const DescType keyAEPropertiesLocal = pProperties;
+		static const DescType kAEAllLocal = kAEAll;
+		AEDesc	every;
+		AEDesc	everyLoginItem;
+		AEDesc	properties;
+		
+		every = kAENull;
+		everyLoginItem = kAENull;
+		properties = kAENull;
+
+		err = AECreateDesc(typeAbsoluteOrdinal, &kAEAllLocal, sizeof(kAEAllLocal), &every);
+		if (err == noErr) {
+			err = CreateObjSpecifier(cLoginItem, (AEDesc *) &kAENull, formAbsolutePosition, &every, false, &everyLoginItem);
+		}
+		if (err == noErr) {
+			err = AECreateDesc(typeType, &keyAEPropertiesLocal, sizeof(keyAEPropertiesLocal), &properties);
+		}
+		if (err == noErr) {
+			err = CreateObjSpecifier(
+				typeProperty, 
+				&everyLoginItem, 
+				formPropertyID,
+				&properties, 
+				false, 
+				&propertiesOfEveryLoginItem);
+		}
+
+		AEDisposeDescQ(&every);
+		AEDisposeDescQ(&everyLoginItem);
+		AEDisposeDescQ(&properties);
+	}
+	
+	// Send event and get reply.
+	
+	if (err == noErr) {
+		err = SendEventToSystemEventsWithParameters(
+			kAECoreSuite,
+			kAEGetData,
+			&reply,
+			keyDirectObject, &propertiesOfEveryLoginItem,
+			0
+		);
+	}
+	
+	// Process reply.
+	
+	if (err == noErr) {
+		err = AEGetParamDesc(&reply, keyDirectObject, typeAEList, &results);
+	}
+	if (err == noErr) {
+		err = CreateCFArrayFromAEDescList(&results, itemsPtr);
+	}
+
+	// Clean up.
+	
+	AEDisposeDescQ(&reply);
+	AEDisposeDescQ(&results);
+	AEDisposeDescQ(&propertiesOfEveryLoginItem);
+	assert( (err == noErr) == (*itemsPtr != NULL) );
+	
+	return err;
+}
+
+extern OSStatus LIAEAddRefAtEnd(const FSRef *item, Boolean hideIt)
+	// See comment in header.
+	//
+	// This routine creates an Apple event that corresponds to the 
+	// AppleScript:
+	//
+	//     make new login item 
+	// 	       with properties {
+	//			   path:<path of item>,
+	//			   hidden:hideIt
+	//		   }
+	//         at end
+	//	
+	// and sends it to System Events.
+{
+	OSStatus 			err;
+	AEDesc				newLoginItem;
+	AERecord			properties;
+	AERecord			endLoc;
+	static const DescType cLoginItemLocal = cLoginItem;
+	
+	assert(item != NULL);
+	
+	newLoginItem = kAENull;
+	endLoc = kAENull;
+	properties = kAENull;
+
+	// Create "new login item" parameter.
+	
+	err = AECreateDesc(typeType, &cLoginItemLocal, sizeof(cLoginItemLocal), &newLoginItem);
+	
+	// Create "with properties" parameter.
+	
+	if (err == noErr) {
+		char		path[1024];
+		AEDesc		pathDesc;
+		
+		pathDesc = kAENull;
+		
+		err = AECreateList(NULL, 0, true, &properties);
+		if (err == noErr) {
+			err = FSRefMakePath(item, (UInt8 *) path, sizeof(path));
+		}
+		
+		// System Events complains if you pass it typeUTF8Text directly, so 
+		// we do the conversion from typeUTF8Text to typeUnicodeText on our 
+		// side of the world.
+		
+		if (err == noErr) {
+			err = AECoercePtr(typeUTF8Text, path, (Size) strlen(path), typeUnicodeText, &pathDesc);
+		}
+		if (err == noErr) {
+			err = AEPutKeyDesc(&properties, propPath, &pathDesc);
+		}
+		if (err == noErr) {
+			err = AEPutKeyPtr(&properties, propHidden, typeBoolean, &hideIt, sizeof(hideIt));
+		}
+		
+		AEDisposeDescQ(&pathDesc);
+	}
+	
+	// Create "at end" parameter.
+	
+	if (err == noErr) {
+		AERecord	end;
+		static const DescType kAEEndLocal = kAEEnd;
+
+		end = kAENull;
+		
+		err = AECreateList(NULL, 0, true, &end);
+		if (err == noErr) {
+			err = AEPutKeyPtr(&end, keyAEObject, typeNull, NULL, 0);
+		}
+		if (err == noErr) {
+			err = AEPutKeyPtr(&end, keyAEPosition, typeEnumerated, &kAEEndLocal, (Size) sizeof(kAEEndLocal));
+		}
+		if (err == noErr) {
+			err = AECoerceDesc(&end, cInsertionLoc, &endLoc);
+		}
+		
+		AEDisposeDescQ(&end);
+	}
+	
+	// Send the event.
+		
+	if (err == noErr) {
+		err = SendEventToSystemEventsWithParameters(
+			kAECoreSuite,
+			kAECreateElement,
+			NULL,
+			keyAEObjectClass, 	&newLoginItem,
+			keyAEPropData, 		&properties,
+			keyAEInsertHere, 	&endLoc,
+			0
+		);
+	}
+
+	// Clean up.
+	
+	AEDisposeDescQ(&newLoginItem);
+	AEDisposeDescQ(&endLoc);
+	AEDisposeDescQ(&properties);
+	
+	return err;
+}
+
+extern OSStatus LIAEAddURLAtEnd(CFURLRef item,     Boolean hideIt)
+	// See comment in header.
+	//
+	// This is implemented as a wrapper around LIAEAddRef.  
+	// I chose to do it this way because an URL can reference a 
+	// file that doesn't except, whereas an FSRef can't, so by 
+	// having the URL routine call the FSRef routine, I naturally 
+	// ensure that the item exists on disk.
+{
+	OSStatus 	err;
+	Boolean		success;
+	FSRef		ref;
+
+	assert(item != NULL);
+		
+	err = noErr;
+	success = CFURLGetFSRef(item, &ref);
+	if ( ! success ) {
+		// I have no idea what went wrong (thanks CF!).  Normally I'd 
+		// return coreFoundationUnknownErr here, but in this case I'm 
+		// going to go out on a limb and say that we have a file not found.
+		err = fnfErr;
+	}
+
+	if (err == noErr) {
+		err = LIAEAddRefAtEnd(&ref, hideIt);
+	}
+	
+	return err;
+}
+
+extern OSStatus LIAERemove(CFIndex itemIndex)
+	// See comment in header.
+	//
+	// This routine creates an Apple event that corresponds to the 
+	// AppleScript:
+	//
+	//     delete login item itemIndex
+	//	
+	// and sends it to System Events.
+{
+	OSStatus	err;
+	long		itemIndexPlusOne;
+	AEDesc		indexDesc;
+	AEDesc		loginItemAtIndex;
+	
+	assert(itemIndex >= 0);
+	
+	indexDesc = kAENull;
+	loginItemAtIndex = kAENull;
+
+	// Build object specifier for "login item X".
+
+	itemIndexPlusOne = itemIndex + 1;	// AppleScript is one-based, CF is zero-based
+	err = AECreateDesc(typeLongInteger, &itemIndexPlusOne, sizeof(itemIndexPlusOne), &indexDesc);
+	if (err == noErr) {
+		err = CreateObjSpecifier(cLoginItem, (AEDesc *) &kAENull, formAbsolutePosition, &indexDesc, false, &loginItemAtIndex);
+	}
+
+	// Send the event.
+
+	if (err == noErr) {
+		err = SendEventToSystemEventsWithParameters(
+			kAECoreSuite,
+			kAEDelete,
+			NULL,
+			keyDirectObject, &loginItemAtIndex,
+			0
+		);
+	}
+
+	// Clean up.
+
+	AEDisposeDescQ(&indexDesc);
+	AEDisposeDescQ(&loginItemAtIndex);
+	
+	return err;
+}
diff --git a/LoginItemsAE.h b/LoginItemsAE.h
new file mode 100644
index 0000000..8cea79e
--- /dev/null
+++ b/LoginItemsAE.h
@@ -0,0 +1,101 @@
+/*
+	File:		LoginItemsAE.h
+
+	Contains:	Login items manipulation via Apple events.
+
+	Copyright:	Copyright (c) 2005 by Apple Computer, Inc., All Rights Reserved.
+
+	Disclaimer:	IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
+				("Apple") in consideration of your agreement to the following terms, and your
+				use, installation, modification or redistribution of this Apple software
+				constitutes acceptance of these terms.  If you do not agree with these terms,
+				please do not use, install, modify or redistribute this Apple software.
+
+				In consideration of your agreement to abide by the following terms, and subject
+				to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
+				copyrights in this original Apple software (the "Apple Software"), to use,
+				reproduce, modify and redistribute the Apple Software, with or without
+				modifications, in source and/or binary forms; provided that if you redistribute
+				the Apple Software in its entirety and without modifications, you must retain
+				this notice and the following text and disclaimers in all such redistributions of
+				the Apple Software.  Neither the name, trademarks, service marks or logos of
+				Apple Computer, Inc. may be used to endorse or promote products derived from the
+				Apple Software without specific prior written permission from Apple.  Except as
+				expressly stated in this notice, no other rights or licenses, express or implied,
+				are granted by Apple herein, including but not limited to any patent rights that
+				may be infringed by your derivative works or by other works in which the Apple
+				Software may be incorporated.
+
+				The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
+				WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
+				WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+				PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
+				COMBINATION WITH YOUR PRODUCTS.
+
+				IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
+				CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+				GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+				ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
+				OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
+				(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
+				ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+	Change History (most recent first):
+
+$Log: LoginItemsAE.h,v $
+Revision 1.1  2005/09/27 12:29:29         
+First checked in.
+
+
+*/
+
+#ifndef _LOGINITEMSAE_H
+#define _LOGINITEMSAE_H
+
+/////////////////////////////////////////////////////////////////
+
+// System prototypes
+
+#include <ApplicationServices/ApplicationServices.h>
+
+/////////////////////////////////////////////////////////////////
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/////////////////////////////////////////////////////////////////
+
+// Keys for the dictionary return by LIAECopyLoginItems.
+
+#define kLIAEURL    CFSTR("URL")		// CFURL
+#define kLIAEHidden CFSTR("Hidden")		// CFBoolean
+
+extern OSStatus LIAECopyLoginItems(CFArrayRef *itemsPtr);
+	// Returns an array of CFDictionaries, each one describing a 
+	// login item.  Each dictionary has two elements, 
+	// kLIAEURL and kLIAEHidden, which are 
+	// documented above.
+	// 
+	// On input,    itemsPtr must not be NULL.
+	// On input,   *itemsPtr must be NULL.
+	// On success, *itemsPtr will be a pointer to a CFArray.
+	// Or error,   *itemsPtr will be NULL.
+
+extern OSStatus LIAEAddRefAtEnd(const FSRef *item, Boolean hideIt);
+extern OSStatus LIAEAddURLAtEnd(CFURLRef item,     Boolean hideIt);
+	// Add a new login item at the end of the list, using either 
+	// an FSRef or a CFURL.  The hideIt parameter controls whether 
+	// the item is hidden when it's launched.
+
+extern OSStatus LIAERemove(CFIndex itemIndex);
+	// Remove a login item.  itemIndex is an index into the array 
+	// of login items as returned by LIAECopyLoginItems.
+
+/////////////////////////////////////////////////////////////////
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/LoginItemsAETest/Info.plist b/LoginItemsAETest/Info.plist
new file mode 100644
index 0000000..b001c26
--- /dev/null
+++ b/LoginItemsAETest/Info.plist
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>EXECUTABLE_NAME</string>
+	<key>CFBundleIdentifier</key>
+	<string>com.apple.LoginItemsAETest</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleVersion</key>
+	<string>1.0</string>
+	<key>CSResourcesFileMapped</key>
+	<true/>
+</dict>
+</plist>
diff --git a/LoginItemsAETest/LoginItemsAETest.c b/LoginItemsAETest/LoginItemsAETest.c
new file mode 100644
index 0000000..59617a4
--- /dev/null
+++ b/LoginItemsAETest/LoginItemsAETest.c
@@ -0,0 +1,613 @@
+/*
+	File:		LoginItemsAETest.c
+
+	Contains:	Test program for LoginItemsAE module.
+
+	Copyright:	Copyright © 2005 by Apple Computer, Inc., All Rights Reserved.
+
+	Disclaimer:	IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
+				("Apple") in consideration of your agreement to the following terms, and your
+				use, installation, modification or redistribution of this Apple software
+				constitutes acceptance of these terms.  If you do not agree with these terms,
+				please do not use, install, modify or redistribute this Apple software.
+
+				In consideration of your agreement to abide by the following terms, and subject
+				to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
+				copyrights in this original Apple software (the "Apple Software"), to use,
+				reproduce, modify and redistribute the Apple Software, with or without
+				modifications, in source and/or binary forms; provided that if you redistribute
+				the Apple Software in its entirety and without modifications, you must retain
+				this notice and the following text and disclaimers in all such redistributions of
+				the Apple Software.  Neither the name, trademarks, service marks or logos of
+				Apple Computer, Inc. may be used to endorse or promote products derived from the
+				Apple Software without specific prior written permission from Apple.  Except as
+				expressly stated in this notice, no other rights or licenses, express or implied,
+				are granted by Apple herein, including but not limited to any patent rights that
+				may be infringed by your derivative works or by other works in which the Apple
+				Software may be incorporated.
+
+				The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
+				WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
+				WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+				PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
+				COMBINATION WITH YOUR PRODUCTS.
+
+				IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
+				CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+				GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+				ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
+				OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
+				(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
+				ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+	Change History (most recent first):
+
+$Log: LoginItemsAETest.c,v $
+Revision 1.1  2005/09/27 12:29:32         
+First checked in.
+
+
+*/
+
+/////////////////////////////////////////////////////////////////
+
+// System interfaces
+
+#include <Carbon/Carbon.h>
+
+#include <assert.h>
+#include <stdio.h>
+
+// Project interfaces
+
+#include "LoginItemsAE.h"
+
+/////////////////////////////////////////////////////////////////
+
+static void CFQRelease(CFTypeRef cf)
+{
+    if (cf != NULL) {
+        CFRelease(cf);
+    }
+}
+
+static void DoAbout(void)
+	// Displays the about box.
+{
+	SInt16 junkHit;
+
+	(void) StandardAlert(
+		kAlertPlainAlert, 
+		"\pLoginItemsTestAE", 
+		"\pA simple program to test LoginItemsAE.\r\rDTS\r\r© 2005 Apple Computer, Inc.", 
+		NULL, 
+		&junkHit
+	);
+}
+
+static void DisplayError(OSStatus err)
+	// Displays a trivial error dialog if err represents an error.
+{
+	if ( (err != noErr) && (err != userCanceledErr) ) {
+        AlertStdCFStringAlertParamRec   params;
+        DialogRef                       dlg;
+        DialogItemIndex                 junkItem;
+        
+        params.version       = kStdCFStringAlertVersionOne;
+        params.movable       = true;
+        params.helpButton    = false;
+        params.defaultText   = (CFStringRef) kAlertDefaultOKText;
+        params.cancelText    = NULL;
+        params.otherText     = NULL;
+        params.defaultButton = kAlertStdAlertOKButton;
+        params.cancelButton  = 0;
+        params.position      = kWindowCenterMainScreen;
+        params.flags         = 0;
+        
+        err = CreateStandardAlert(
+            kAlertStopAlert, 
+            CFSTR("LoginItemsAE got an error."), 
+            CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), err),
+            &params, 
+            &dlg
+        );
+        if (err == noErr) {
+            err = RunStandardAlert(dlg, NULL, &junkItem);
+        }
+        assert(err == noErr);
+	}
+}
+
+static ControlRef	gDataControl;		// a data browser control
+static ControlRef	gAddHiddenControl;	// the "Add Hidden" checkbox
+static WindowRef 	gMainWindow;
+
+static CFArrayRef	gItems;
+
+static pascal OSStatus DataBrowserDataCallback(
+	ControlRef 				browser, 
+	DataBrowserItemID 		item, 
+	DataBrowserPropertyID 	property, 
+	DataBrowserItemDataRef	itemData, 
+	Boolean 				setValue
+)
+	// Called by the data browser control to get information about the 
+	// data that it's displaying.  item is the row in the list, which in this 
+	// case is the index (plus one) into the gItems CFArray that represents 
+	// the login items list.  property is the column in the list; these values 
+	// are set in the .nib file.  When asked for data, the routine looks up 
+	// the item'th element of gItems and then extracts the appropriate key 
+	// from that dictionary.
+{
+	OSStatus		err;
+	CFDictionaryRef	dict;
+	CFBooleanRef	hidden;
+	CFURLRef		url;
+	CFStringRef		str;
+	
+	assert(browser == gDataControl);
+	assert( (item != kDataBrowserNoItem) && (item <= CFArrayGetCount(gItems)) );
+	assert( (property < 1024) || (property == 'hidn') || (property == 'URL ') );
+	assert( ! setValue );
+	
+	// gItems can only be NULL before the first call to DoRefresh as the 
+	// application starts up.  Data browser should never be calling us to 
+	// get information about items during that time, because we haven't 
+	// added any items to the data browser.  Thus, we shouldn't be called 
+	// when gItems is NULL.
+
+	assert( gItems != NULL );
+	
+	switch (property) {
+		case 'hidn':
+			dict = (CFDictionaryRef) CFArrayGetValueAtIndex(gItems, (CFIndex) (item - 1));
+			assert( (dict != NULL) && (CFGetTypeID(dict) == CFDictionaryGetTypeID()) );
+			
+			hidden = (CFBooleanRef) CFDictionaryGetValue(dict, kLIAEHidden);
+			assert( (hidden != NULL) && (CFGetTypeID(hidden) == CFBooleanGetTypeID()) );
+
+			err = SetDataBrowserItemDataText(
+				itemData, 
+				(CFBooleanGetValue(hidden) ? CFSTR("yes") : CFSTR("no"))
+			);
+			break;
+		case 'URL ':
+			dict = (CFDictionaryRef) CFArrayGetValueAtIndex(gItems, (CFIndex) (item - 1));
+			assert( (dict != NULL) && (CFGetTypeID(dict) == CFDictionaryGetTypeID()) );
+
+			url = (CFURLRef) CFDictionaryGetValue(dict, kLIAEURL);
+			assert( (url != NULL) && (CFGetTypeID(url) == CFURLGetTypeID()) );
+
+			str = CFURLGetString(url);
+			
+			err = SetDataBrowserItemDataText(itemData, str);
+			break;
+		default:
+			err = noErr;
+			break;
+	}
+
+	assert(err == noErr);
+
+	return err;
+}
+
+static void DoRefresh(void)
+	// Called in response to a click of the "Refresh" button.  
+	// Also called to refresh the list after other actions have 
+	// changed it.  Also called at application startup to 
+	// establish an initial value for the list.
+{
+	OSStatus 			err;
+	CFArrayRef			items;
+	CFIndex				itemCount;
+	CFIndex				itemIndex;
+	DataBrowserItemID *	itemsIDArray;
+	
+	itemsIDArray = NULL;
+	items = NULL;
+	
+	// Get the array from LoginItemsAE.
+	
+	err = LIAECopyLoginItems(&items);
+	if (err == noErr) {
+	
+		// Swap it into gItems.
+		
+		CFQRelease(gItems);
+		gItems = items;
+		items = NULL;		// to prevent the release at the end of this function
+	
+		// Remove any existing items.
+			
+		if (err == noErr) {
+			err = RemoveDataBrowserItems(
+				gDataControl,
+				kDataBrowserNoItem,					// from root
+				0,									// all items
+				NULL,								//  "    "
+				kDataBrowserItemNoProperty
+			);
+		}	
+		
+		// Add the new items.
+		
+		if (err == noErr) {
+			itemCount = CFArrayGetCount(gItems);
+		
+			itemsIDArray = calloc(itemCount, sizeof(DataBrowserItemID));
+			if (itemsIDArray == NULL) {
+				err = memFullErr;
+			}
+		}
+		if (err == noErr) {
+		
+			// Each item ID is the item's index into the gItems array, 
+			// plus one because a value of 0 is invalid (it's kDataBrowserNoItem).
+			
+			for (itemIndex = 0; itemIndex < itemCount; itemIndex++) {
+				itemsIDArray[itemIndex] = (DataBrowserItemID) (itemIndex + 1);
+			}
+		
+			err = AddDataBrowserItems(
+				gDataControl,
+				kDataBrowserNoItem,					// to root
+				(UInt32) itemCount,
+				itemsIDArray,
+				kDataBrowserItemNoProperty
+			);
+		}
+	}
+	
+	// Clean up.
+	
+	CFQRelease(items);
+	free(itemsIDArray);
+
+	DisplayError(err);
+}
+
+static NavEventUPP gAddNavEventUPP;			// AddNavEvent
+
+static pascal void AddNavEvent(
+	NavEventCallbackMessage callBackSelector, 
+	NavCBRecPtr 			callBackParms, 
+	void *					callBackUD
+)
+	// Called by Navigation Services when interesting things happen 
+	// in our Nav dialog (which is displayed when the user clicks 
+	// the "Add" button).  In this case we're primarily interested 
+	// in two events: the user action of the user clicking the Choose 
+	// button of the Nav dialog, and the dialog being torn down.
+{
+	#pragma unused(callBackUD)
+	OSStatus		err;
+	OSStatus		junk;
+	NavDialogRef 	navDialog;
+	
+	navDialog = callBackParms->context;
+	assert(navDialog != NULL);
+	
+	switch (callBackSelector) {
+		case kNavCBUserAction:
+			switch ( NavDialogGetUserAction(navDialog) ) {
+				case kNavUserActionChoose:
+					{
+						NavReplyRecord 	reply;
+						AEKeyword 		junkKeyword;
+						DescType		junkType;
+						Size			junkSize;
+						
+						err = NavDialogGetReply(navDialog, &reply);
+						if (err == noErr) {
+							FSRef	chosenItem;
+							
+							// In the debug build, verify that only one items is 
+							// selected.
+							
+							#if ! defined(NDEBUG)
+								{
+									long selectionCount;
+									
+									assert( 
+										   (AECountItems( &reply.selection, &selectionCount) == noErr)
+										&& (selectionCount == 1)
+									);
+								}
+							#endif
+
+							// Get the selected item.
+							
+							err = AEGetNthPtr(
+								&reply.selection, 
+								1, 
+								typeFSRef, 
+								&junkKeyword, 
+								&junkType, 
+								&chosenItem, 
+								sizeof(chosenItem), 
+								&junkSize
+							);
+							
+							// Use LoginItemsAE to add it to the list.
+							
+							if (err == noErr) {
+								err = LIAEAddRefAtEnd(
+									&chosenItem, 
+									GetControlValue(gAddHiddenControl) != 0
+								);
+							}
+						
+							junk = NavDisposeReply(&reply);
+							assert(junk == noErr);
+							
+							if (err == noErr) {
+								DoRefresh();
+							} else {
+								DisplayError(err);
+							}						
+						}
+					}
+					break;
+				default:
+					// do nothing
+					break;
+			}
+			break;
+		case kNavCBTerminate:
+			NavDialogDispose(navDialog);
+			break;
+		default:
+			// do nothing
+			break;
+	}
+}
+
+static void DoAddTest(void)
+	// Called in response to a click of the "Add" button. 
+{
+	OSStatus 					err;
+	NavDialogCreationOptions	navOptions;
+	NavDialogRef				navDialog;
+
+	navDialog = NULL;
+	
+	if (gAddNavEventUPP == NULL) {
+		gAddNavEventUPP = NewNavEventUPP(AddNavEvent);
+		assert(gAddNavEventUPP != NULL);
+	}
+
+	// Create and run a Nav choose object dialog (which lets you 
+	// choose a file, folder or volume).
+	
+	err = NavGetDefaultDialogCreationOptions(&navOptions);
+	if (err == noErr) {
+		navOptions.optionFlags  |= kNavSupportPackages;
+		navOptions.modality      = kWindowModalityWindowModal;
+		navOptions.parentWindow  = gMainWindow;
+		
+		err = NavCreateChooseObjectDialog(
+			&navOptions,					// inOptions
+			gAddNavEventUPP,
+			NULL,							// inPreviewProc
+			NULL,							// inFilterProc
+			NULL,							// inClientData
+			&navDialog
+		);
+	}
+	if (err == noErr) {
+		err = NavDialogRun(navDialog);
+		
+		// The process of adding the item continues in AddNavEvent 
+		// when the user has chosen an item to add.
+	}
+
+	// Clean up.
+
+	// Dispose the Nav dialog if we didn't manage to start it running.
+	
+	if (err != noErr) {
+		if (navDialog != NULL) {
+			NavDialogDispose(navDialog);
+		}
+	}
+
+	DisplayError(err);
+}
+
+static void DoRemoveTest(void)
+	// Called in response to a click of the "Remove" button. 
+{
+	OSStatus	err;
+	CFIndex 	itemCount;
+	CFIndex 	itemIndex;
+	Boolean		found;
+	
+	// Find the index of the selected item in the data browser 
+	// control.
+	
+	itemCount = CFArrayGetCount(gItems);
+	itemIndex = 0;
+	found = false;
+	while ( (itemIndex < itemCount) && ! found ) {
+		found = IsDataBrowserItemSelected(gDataControl, (DataBrowserItemID) itemIndex + 1);;
+		
+		if ( ! found ) {
+			itemIndex += 1;
+		}
+	}
+	
+	// Use LIAE to remove it.
+	
+	if (found) {
+		err = LIAERemove(itemIndex);
+	} else {
+		// It's not an error to have no selection because we don't dynamically 
+		// enable/disable the Remove button.
+		err = noErr;
+	}
+	
+	if (err == noErr) {
+		DoRefresh();
+	} else {
+		DisplayError(err);
+	}	
+}
+
+static EventHandlerUPP gApplicationEventHandlerUPP;		// -> ApplicationEventHandler
+
+static const EventTypeSpec kApplicationEvents[] = { {kEventClassCommand, kEventCommandProcess} };
+
+static pascal OSStatus ApplicationEventHandler(EventHandlerCallRef inHandlerCallRef, 
+											   EventRef inEvent, void *inUserData)
+	// Dispatches HICommands to their implementations.
+{
+	OSStatus 	err;
+	HICommand 	command;
+	#pragma unused(inHandlerCallRef)
+	#pragma unused(inUserData)
+	
+	assert( GetEventClass(inEvent) == kEventClassCommand  );
+	assert( GetEventKind(inEvent)  == kEventCommandProcess);
+	
+	err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(command), NULL, &command);
+	if (err == noErr) {
+		switch (command.commandID) {
+			case kHICommandAbout:
+				DoAbout();
+				break;
+			case 'DShw':
+				DoRefresh();
+				break;
+			case 'DAdd':
+				DoAddTest();
+				break;
+			case 'DRem':
+				DoRemoveTest();
+				break;
+			default:
+				err = eventNotHandledErr;
+				break;
+		}
+	}
+	
+	return err;
+}
+
+static OSStatus SetupUserInterface(void)
+	// Create a user interface from our NIB.
+{
+	OSStatus 	err;
+	IBNibRef 	nibRef;
+	Handle		menuBar;
+
+	nibRef  = NULL;
+	menuBar = NULL;
+		
+	err = CreateNibReference(CFSTR("LoginItemsAETest"), &nibRef);
+	if (err == noErr) {
+		err = CreateMenuBarFromNib(nibRef, CFSTR("MenuBar"), &menuBar);
+	}
+	if (err == noErr) {
+		SetMenuBar(menuBar);
+	}
+	if (err == noErr) {
+		err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &gMainWindow);
+	}
+	if (err == noErr) {
+		ControlID theID;
+
+		theID.signature = 'HDCB';
+		theID.id = 0;	
+
+		err = GetControlByID(gMainWindow, &theID, &gAddHiddenControl);
+	}
+
+	// Find and set up the data browser control.
+	
+	if (err == noErr) {
+		ControlID theID;
+
+		theID.signature = 'DATA';
+		theID.id = 0;	
+
+		err = GetControlByID(gMainWindow, &theID, &gDataControl);
+	}
+	if (err == noErr) {
+		DataBrowserCallbacks callbacks;
+		
+		callbacks.version = kDataBrowserLatestCallbacks;
+		err = InitDataBrowserCallbacks(&callbacks);
+
+		if (err == noErr) {
+			callbacks.u.v1.itemDataCallback = NewDataBrowserItemDataUPP(DataBrowserDataCallback);
+
+			err = SetDataBrowserCallbacks(gDataControl, &callbacks);
+		}
+	}
+	if (err == noErr) {
+		DoRefresh();
+	}
+	
+	if (err == noErr) {
+		ShowWindow(gMainWindow);
+	}
+  	
+	if (nibRef != NULL) {
+		DisposeNibReference(nibRef);
+	}
+	if (menuBar != NULL) {
+		DisposeHandle(menuBar);
+	}
+	return err;
+}
+
+int main(void)
+{
+	OSStatus    err;
+    UInt32      response;
+        
+    err = Gestalt(gestaltSystemVersion, (long *) &response);
+    if (err == noErr) {
+        if ( response < 0x1020 ) {
+            SInt16 junkHit;
+            
+            (void) StandardAlert(
+                kAlertStopAlert, 
+                "\pLoginItemsAETest requires Mac OS X 10.2 or later.", 
+                "\p",
+                NULL, 
+                &junkHit
+            );
+            
+            err = userCanceledErr;
+        }
+    }
+    
+	// Start up the UI.
+	
+    if (err == noErr) {
+        err = SetupUserInterface();
+    }
+	
+	// Install our HICommand handler.
+	
+	if (err == noErr) {
+		gApplicationEventHandlerUPP = NewEventHandlerUPP(ApplicationEventHandler);
+		assert(gApplicationEventHandlerUPP != NULL);
+
+		err = InstallApplicationEventHandler(gApplicationEventHandlerUPP, 
+											 GetEventTypeCount(kApplicationEvents), 
+											 kApplicationEvents, NULL, NULL);
+	}
+	
+	// Run the application.
+	
+	if (err == noErr) {
+		RunApplicationEventLoop();
+	}
+
+	DisplayError(err);
+
+	return 0;
+}
diff --git a/LoginItemsAETest/LoginItemsAETest.nib/classes.nib b/LoginItemsAETest/LoginItemsAETest.nib/classes.nib
new file mode 100644
index 0000000..ea58db1
--- /dev/null
+++ b/LoginItemsAETest/LoginItemsAETest.nib/classes.nib
@@ -0,0 +1,4 @@
+{
+IBClasses = ();
+IBVersion = 1;
+}
diff --git a/LoginItemsAETest/LoginItemsAETest.nib/info.nib b/LoginItemsAETest/LoginItemsAETest.nib/info.nib
new file mode 100644
index 0000000..208f02a
--- /dev/null
+++ b/LoginItemsAETest/LoginItemsAETest.nib/info.nib
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>69 10 356 240 0 0 1280 1002 </string>
+	<key>IBEditorPositions</key>
+	<dict>
+		<key>29</key>
+		<string>52 213 264 44 0 0 1280 1002 </string>
+	</dict>
+	<key>IBFramework Version</key>
+	<string>439.0</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>29</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>8A428</string>
+	<key>targetFramework</key>
+	<string>IBCarbonFramework</string>
+</dict>
+</plist>
diff --git a/LoginItemsAETest/LoginItemsAETest.nib/objects.xib b/LoginItemsAETest/LoginItemsAETest.nib/objects.xib
new file mode 100644
index 0000000..5c2ef09
--- /dev/null
+++ b/LoginItemsAETest/LoginItemsAETest.nib/objects.xib
@@ -0,0 +1,236 @@
+<?xml version="1.0" standalone="yes"?>
+<object class="NSIBObjectData">
+  <string name="targetFramework">IBCarbonFramework</string>
+  <object name="rootObject" class="NSCustomObject" id="1">
+    <string name="customClass">NSApplication</string>
+  </object>
+  <array count="28" name="allObjects">
+    <object class="IBCarbonMenu" id="29">
+      <string name="title">MoreSecurityTest</string>
+      <array count="3" name="items">
+        <object class="IBCarbonMenuItem" id="185">
+          <string name="title">LoginItemsAETest</string>
+          <object name="submenu" class="IBCarbonMenu" id="184">
+            <string name="title">LoginItemsAETest</string>
+            <array count="1" name="items">
+              <object class="IBCarbonMenuItem" id="187">
+                <string name="title">About LoginItemsAETest</string>
+                <int name="keyEquivalentModifier">0</int>
+                <ostype name="command">abou</ostype>
+              </object>
+            </array>
+            <string name="name">_NSAppleMenu</string>
+          </object>
+        </object>
+        <object class="IBCarbonMenuItem" id="152">
+          <string name="title">Edit</string>
+          <object name="submenu" class="IBCarbonMenu" id="147">
+            <string name="title">Edit</string>
+            <array count="8" name="items">
+              <object class="IBCarbonMenuItem" id="141">
+                <string name="title">Undo</string>
+                <string name="keyEquivalent">z</string>
+                <ostype name="command">undo</ostype>
+              </object>
+              <object class="IBCarbonMenuItem" id="146">
+                <string name="title">Redo</string>
+                <string name="keyEquivalent">Z</string>
+                <ostype name="command">redo</ostype>
+              </object>
+              <object class="IBCarbonMenuItem" id="142">
+                <boolean name="separator">TRUE</boolean>
+              </object>
+              <object class="IBCarbonMenuItem" id="143">
+                <string name="title">Cut</string>
+                <string name="keyEquivalent">x</string>
+                <ostype name="command">cut </ostype>
+              </object>
+              <object class="IBCarbonMenuItem" id="149">
+                <string name="title">Copy</string>
+                <string name="keyEquivalent">c</string>
+                <ostype name="command">copy</ostype>
+              </object>
+              <object class="IBCarbonMenuItem" id="144">
+                <string name="title">Paste</string>
+                <string name="keyEquivalent">v</string>
+                <ostype name="command">past</ostype>
+              </object>
+              <object class="IBCarbonMenuItem" id="151">
+                <string name="title">Delete</string>
+                <ostype name="command">clea</ostype>
+              </object>
+              <object class="IBCarbonMenuItem" id="148">
+                <string name="title">Select All</string>
+                <string name="keyEquivalent">a</string>
+                <ostype name="command">sall</ostype>
+              </object>
+            </array>
+          </object>
+        </object>
+        <object class="IBCarbonMenuItem" id="153">
+          <string name="title">Window</string>
+          <object name="submenu" class="IBCarbonMenu" id="154">
+            <string name="title">Window</string>
+            <array count="5" name="items">
+              <object class="IBCarbonMenuItem" id="155">
+                <boolean name="dynamic">TRUE</boolean>
+                <string name="title">Minimize Window</string>
+                <string name="keyEquivalent">m</string>
+                <ostype name="command">mini</ostype>
+              </object>
+              <object class="IBCarbonMenuItem" id="188">
+                <boolean name="dynamic">TRUE</boolean>
+                <string name="title">Minimize All Windows</string>
+                <string name="keyEquivalent">m</string>
+                <int name="keyEquivalentModifier">1572864</int>
+                <ostype name="command">mina</ostype>
+              </object>
+              <object class="IBCarbonMenuItem" id="156">
+                <boolean name="separator">TRUE</boolean>
+              </object>
+              <object class="IBCarbonMenuItem" id="157">
+                <boolean name="dynamic">TRUE</boolean>
+                <string name="title">Bring All to Front</string>
+                <ostype name="command">bfrt</ostype>
+              </object>
+              <object class="IBCarbonMenuItem" id="189">
+                <boolean name="dynamic">TRUE</boolean>
+                <string name="title">Arrange in Front</string>
+                <int name="keyEquivalentModifier">1572864</int>
+                <ostype name="command">frnt</ostype>
+              </object>
+            </array>
+            <string name="name">_NSWindowsMenu</string>
+          </object>
+        </object>
+      </array>
+      <string name="name">_NSMainMenu</string>
+    </object>
+    <reference idRef="141"/>
+    <reference idRef="142"/>
+    <reference idRef="143"/>
+    <reference idRef="144"/>
+    <reference idRef="146"/>
+    <reference idRef="147"/>
+    <reference idRef="148"/>
+    <reference idRef="149"/>
+    <reference idRef="151"/>
+    <reference idRef="152"/>
+    <reference idRef="153"/>
+    <reference idRef="154"/>
+    <reference idRef="155"/>
+    <reference idRef="156"/>
+    <reference idRef="157"/>
+    <object class="IBCarbonWindow" id="166">
+      <string name="windowRect">54 14 454 614 </string>
+      <string name="title">LoginItemsTestAE</string>
+      <object name="rootControl" class="IBCarbonRootControl" id="167">
+        <string name="bounds">0 0 400 600 </string>
+        <array count="5" name="subviews">
+          <object class="IBCarbonButton" id="190">
+            <string name="bounds">360 368 380 468 </string>
+            <string name="title">Add…</string>
+            <ostype name="command">DAdd</ostype>
+          </object>
+          <object class="IBCarbonButton" id="192">
+            <string name="bounds">360 480 380 580 </string>
+            <string name="title">Remove</string>
+            <ostype name="command">DRem</ostype>
+          </object>
+          <object class="IBCarbonDBListView" id="193">
+            <string name="bounds">20 20 340 580 </string>
+            <ostype name="controlSignature">DATA</ostype>
+            <int name="hiliteStyle">1</int>
+            <int name="selectionFlags">11</int>
+            <array count="2" name="listViewColumns">
+              <object class="IBCarbonDBListViewColumn">
+                <ostype name="propertyID">hidn</ostype>
+                <int name="propertyFlags">458752</int>
+                <int name="sortOrder">1</int>
+                <int name="minWidth">80</int>
+                <int name="maxWidth">80</int>
+                <int name="textAlignment">-2</int>
+                <string name="titleString">Hidden</string>
+              </object>
+              <object class="IBCarbonDBListViewColumn">
+                <ostype name="propertyID">URL </ostype>
+                <int name="propertyFlags">458752</int>
+                <int name="sortOrder">1</int>
+                <int name="minWidth">450</int>
+                <int name="maxWidth">450</int>
+                <int name="textAlignment">-2</int>
+                <string name="titleString">URL</string>
+              </object>
+            </array>
+          </object>
+          <object class="IBCarbonButton" id="194">
+            <string name="bounds">360 20 380 120 </string>
+            <string name="title">Refresh</string>
+            <ostype name="command">DShw</ostype>
+          </object>
+          <object class="IBCarbonCheckBox" id="195">
+            <string name="bounds">363 244 377 360 </string>
+            <ostype name="controlSignature">HDCB</ostype>
+            <string name="title">Add Hidden</string>
+          </object>
+        </array>
+      </object>
+      <boolean name="hasCloseBox">FALSE</boolean>
+      <boolean name="hasHorizontalZoom">FALSE</boolean>
+      <boolean name="isResizable">FALSE</boolean>
+      <boolean name="hasVerticalZoom">FALSE</boolean>
+    </object>
+    <reference idRef="167"/>
+    <reference idRef="184"/>
+    <reference idRef="185"/>
+    <reference idRef="187"/>
+    <reference idRef="188"/>
+    <reference idRef="189"/>
+    <reference idRef="190"/>
+    <reference idRef="192"/>
+    <reference idRef="193"/>
+    <reference idRef="194"/>
+    <reference idRef="195"/>
+  </array>
+  <array count="28" name="allParents">
+    <reference idRef="1"/>
+    <reference idRef="147"/>
+    <reference idRef="147"/>
+    <reference idRef="147"/>
+    <reference idRef="147"/>
+    <reference idRef="147"/>
+    <reference idRef="152"/>
+    <reference idRef="147"/>
+    <reference idRef="147"/>
+    <reference idRef="147"/>
+    <reference idRef="29"/>
+    <reference idRef="29"/>
+    <reference idRef="153"/>
+    <reference idRef="154"/>
+    <reference idRef="154"/>
+    <reference idRef="154"/>
+    <reference idRef="1"/>
+    <reference idRef="166"/>
+    <reference idRef="185"/>
+    <reference idRef="29"/>
+    <reference idRef="184"/>
+    <reference idRef="154"/>
+    <reference idRef="154"/>
+    <reference idRef="167"/>
+    <reference idRef="167"/>
+    <reference idRef="167"/>
+    <reference idRef="167"/>
+    <reference idRef="167"/>
+  </array>
+  <dictionary count="4" name="nameTable">
+    <string>Files Owner</string>
+    <reference idRef="1"/>
+    <string>ListView</string>
+    <reference idRef="193"/>
+    <string>MainWindow</string>
+    <reference idRef="166"/>
+    <string>MenuBar</string>
+    <reference idRef="29"/>
+  </dictionary>
+  <unsigned_int name="nextObjectID">196</unsigned_int>
+</object>
diff --git a/LoginItemsAETest/LoginItemsAETest.xcodeproj/project.pbxproj b/LoginItemsAETest/LoginItemsAETest.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..1f48d28
--- /dev/null
+++ b/LoginItemsAETest/LoginItemsAETest.xcodeproj/project.pbxproj
@@ -0,0 +1,365 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 44;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		8D0C4E920486CD37000505A6 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20286C33FDCF999611CA2CEA /* Carbon.framework */; };
+		E48AE8960643166D00066401 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = E48AE8950643166D00066401 /* Info.plist */; };
+		E4D1EB6308E0720400F1800B /* LoginItemsAE.h in Headers */ = {isa = PBXBuildFile; fileRef = E4FBB75208E03B3500B1AAFA /* LoginItemsAE.h */; };
+		E4D1EB6608E0720400F1800B /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = E48AE8950643166D00066401 /* Info.plist */; };
+		E4D1EB6708E0720400F1800B /* LoginItemsAETest.nib in Resources */ = {isa = PBXBuildFile; fileRef = E4FBB7C408E0420800B1AAFA /* LoginItemsAETest.nib */; };
+		E4D1EB6908E0720400F1800B /* LoginItemsAE.c in Sources */ = {isa = PBXBuildFile; fileRef = E4FBB75308E03B3500B1AAFA /* LoginItemsAE.c */; };
+		E4D1EB6A08E0720400F1800B /* LoginItemsAETest.c in Sources */ = {isa = PBXBuildFile; fileRef = E4FBB7BA08E041CC00B1AAFA /* LoginItemsAETest.c */; };
+		E4D1EB6C08E0720400F1800B /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20286C33FDCF999611CA2CEA /* Carbon.framework */; };
+		E4FBB75408E03B3500B1AAFA /* LoginItemsAE.h in Headers */ = {isa = PBXBuildFile; fileRef = E4FBB75208E03B3500B1AAFA /* LoginItemsAE.h */; };
+		E4FBB75508E03B3500B1AAFA /* LoginItemsAE.c in Sources */ = {isa = PBXBuildFile; fileRef = E4FBB75308E03B3500B1AAFA /* LoginItemsAE.c */; };
+		E4FBB7BC08E041CC00B1AAFA /* LoginItemsAETest.c in Sources */ = {isa = PBXBuildFile; fileRef = E4FBB7BA08E041CC00B1AAFA /* LoginItemsAETest.c */; };
+		E4FBB7C508E0420800B1AAFA /* LoginItemsAETest.nib in Resources */ = {isa = PBXBuildFile; fileRef = E4FBB7C408E0420800B1AAFA /* LoginItemsAETest.nib */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXBuildRule section */
+		E4D1EB7408E0721000F1800B /* PBXBuildRule */ = {
+			isa = PBXBuildRule;
+			compilerSpec = com.apple.compilers.gcc.3_3;
+			fileType = sourcecode.c;
+			isEditable = 1;
+			outputFiles = (
+			);
+		};
+/* End PBXBuildRule section */
+
+/* Begin PBXFileReference section */
+		20286C33FDCF999611CA2CEA /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = "<absolute>"; };
+		E48AE8950643166D00066401 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = Info.plist; sourceTree = "<group>"; };
+		E4D1EB7108E0720400F1800B /* LoginItemsAETest-compat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "LoginItemsAETest-compat.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		E4FBB75008E03B2C00B1AAFA /* LoginItemsAETest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LoginItemsAETest.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		E4FBB75208E03B3500B1AAFA /* LoginItemsAE.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = LoginItemsAE.h; path = ../LoginItemsAE.h; sourceTree = SOURCE_ROOT; };
+		E4FBB75308E03B3500B1AAFA /* LoginItemsAE.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = LoginItemsAE.c; path = ../LoginItemsAE.c; sourceTree = SOURCE_ROOT; };
+		E4FBB7BA08E041CC00B1AAFA /* LoginItemsAETest.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = LoginItemsAETest.c; sourceTree = "<group>"; };
+		E4FBB7C408E0420800B1AAFA /* LoginItemsAETest.nib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; path = LoginItemsAETest.nib; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		8D0C4E910486CD37000505A6 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				8D0C4E920486CD37000505A6 /* Carbon.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E4D1EB6B08E0720400F1800B /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E4D1EB6C08E0720400F1800B /* Carbon.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		195DF8CFFE9D517E11CA2CBB /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				E4FBB75008E03B2C00B1AAFA /* LoginItemsAETest.app */,
+				E4D1EB7108E0720400F1800B /* LoginItemsAETest-compat.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		20286C29FDCF999611CA2CEA /* MoreLoginItemsTest */ = {
+			isa = PBXGroup;
+			children = (
+				E4FBB75208E03B3500B1AAFA /* LoginItemsAE.h */,
+				E4FBB75308E03B3500B1AAFA /* LoginItemsAE.c */,
+				E4FBB7C308E041F600B1AAFA /* LoginItemsAETest */,
+				20286C33FDCF999611CA2CEA /* Carbon.framework */,
+				195DF8CFFE9D517E11CA2CBB /* Products */,
+			);
+			name = MoreLoginItemsTest;
+			sourceTree = "<group>";
+		};
+		E4FBB7C308E041F600B1AAFA /* LoginItemsAETest */ = {
+			isa = PBXGroup;
+			children = (
+				E4FBB7BA08E041CC00B1AAFA /* LoginItemsAETest.c */,
+				E48AE8950643166D00066401 /* Info.plist */,
+				E4FBB7C408E0420800B1AAFA /* LoginItemsAETest.nib */,
+			);
+			name = LoginItemsAETest;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		8D0C4E8A0486CD37000505A6 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E4FBB75408E03B3500B1AAFA /* LoginItemsAE.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E4D1EB6208E0720400F1800B /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E4D1EB6308E0720400F1800B /* LoginItemsAE.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		8D0C4E890486CD37000505A6 /* LoginItemsAETest */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = E4FBB74608E03B2500B1AAFA /* Build configuration list for PBXNativeTarget "LoginItemsAETest" */;
+			buildPhases = (
+				8D0C4E8A0486CD37000505A6 /* Headers */,
+				8D0C4E8C0486CD37000505A6 /* Resources */,
+				8D0C4E8F0486CD37000505A6 /* Sources */,
+				8D0C4E910486CD37000505A6 /* Frameworks */,
+				8D0C4E940486CD37000505A6 /* Rez */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = LoginItemsAETest;
+			productInstallPath = "$(HOME)/Applications";
+			productName = MoreLoginItemsTest;
+			productReference = E4FBB75008E03B2C00B1AAFA /* LoginItemsAETest.app */;
+			productType = "com.apple.product-type.application";
+		};
+		E4D1EB6108E0720400F1800B /* LoginItemsAETest-compat */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = E4D1EB6E08E0720400F1800B /* Build configuration list for PBXNativeTarget "LoginItemsAETest-compat" */;
+			buildPhases = (
+				E4D1EB6208E0720400F1800B /* Headers */,
+				E4D1EB6508E0720400F1800B /* Resources */,
+				E4D1EB6808E0720400F1800B /* Sources */,
+				E4D1EB6B08E0720400F1800B /* Frameworks */,
+				E4D1EB6D08E0720400F1800B /* Rez */,
+			);
+			buildRules = (
+				E4D1EB7408E0721000F1800B /* PBXBuildRule */,
+			);
+			dependencies = (
+			);
+			name = "LoginItemsAETest-compat";
+			productInstallPath = "$(HOME)/Applications";
+			productName = MoreLoginItemsTest;
+			productReference = E4D1EB7108E0720400F1800B /* LoginItemsAETest-compat.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		20286C28FDCF999611CA2CEA /* Project object */ = {
+			isa = PBXProject;
+			buildConfigurationList = E4FBB74A08E03B2500B1AAFA /* Build configuration list for PBXProject "LoginItemsAETest" */;
+			compatibilityVersion = "Xcode 3.0";
+			hasScannedForEncodings = 1;
+			mainGroup = 20286C29FDCF999611CA2CEA /* MoreLoginItemsTest */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				8D0C4E890486CD37000505A6 /* LoginItemsAETest */,
+				E4D1EB6108E0720400F1800B /* LoginItemsAETest-compat */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		8D0C4E8C0486CD37000505A6 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E48AE8960643166D00066401 /* Info.plist in Resources */,
+				E4FBB7C508E0420800B1AAFA /* LoginItemsAETest.nib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E4D1EB6508E0720400F1800B /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E4D1EB6608E0720400F1800B /* Info.plist in Resources */,
+				E4D1EB6708E0720400F1800B /* LoginItemsAETest.nib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXRezBuildPhase section */
+		8D0C4E940486CD37000505A6 /* Rez */ = {
+			isa = PBXRezBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E4D1EB6D08E0720400F1800B /* Rez */ = {
+			isa = PBXRezBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXRezBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		8D0C4E8F0486CD37000505A6 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E4FBB75508E03B3500B1AAFA /* LoginItemsAE.c in Sources */,
+				E4FBB7BC08E041CC00B1AAFA /* LoginItemsAETest.c in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E4D1EB6808E0720400F1800B /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E4D1EB6908E0720400F1800B /* LoginItemsAE.c in Sources */,
+				E4D1EB6A08E0720400F1800B /* LoginItemsAETest.c in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		E4D1EB6F08E0720400F1800B /* Development */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = ppc;
+				INFOPLIST_FILE = Info.plist;
+				INFOPLIST_PREPROCESS = YES;
+				INFOPLIST_PREPROCESSOR_DEFINITIONS = "EXECUTABLE_NAME=LoginItemsAETest-compat";
+				PRODUCT_NAME = "LoginItemsAETest-compat";
+				WRAPPER_EXTENSION = app;
+			};
+			name = Development;
+		};
+		E4D1EB7008E0720400F1800B /* Deployment */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = ppc;
+				INFOPLIST_FILE = Info.plist;
+				INFOPLIST_PREPROCESS = YES;
+				INFOPLIST_PREPROCESSOR_DEFINITIONS = "EXECUTABLE_NAME=LoginItemsAETest-compat";
+				PRODUCT_NAME = "LoginItemsAETest-compat";
+				WRAPPER_EXTENSION = app;
+			};
+			name = Deployment;
+		};
+		E4FBB74708E03B2500B1AAFA /* Development */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				INFOPLIST_FILE = Info.plist;
+				INFOPLIST_PREPROCESS = YES;
+				INFOPLIST_PREPROCESSOR_DEFINITIONS = "EXECUTABLE_NAME=LoginItemsAETest";
+				PRODUCT_NAME = LoginItemsAETest;
+				WRAPPER_EXTENSION = app;
+			};
+			name = Development;
+		};
+		E4FBB74808E03B2500B1AAFA /* Deployment */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				INFOPLIST_FILE = Info.plist;
+				INFOPLIST_PREPROCESS = YES;
+				INFOPLIST_PREPROCESSOR_DEFINITIONS = "EXECUTABLE_NAME=LoginItemsAETest";
+				PRODUCT_NAME = LoginItemsAETest;
+				WRAPPER_EXTENSION = app;
+			};
+			name = Deployment;
+		};
+		E4FBB74B08E03B2500B1AAFA /* Development */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = (
+					ppc,
+					i386,
+				);
+				COPY_PHASE_STRIP = YES;
+				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = "";
+				GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_LABEL = YES;
+				GCC_WARN_UNUSED_PARAMETER = YES;
+				GCC_WARN_UNUSED_VALUE = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				 
+				PREBINDING = NO;
+				
+				WARNING_CFLAGS = "-Wall";
+			};
+			name = Development;
+		};
+		E4FBB74C08E03B2500B1AAFA /* Deployment */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = (
+					ppc,
+					i386,
+				);
+				COPY_PHASE_STRIP = NO;
+				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
+				GCC_OPTIMIZATION_LEVEL = s;
+				GCC_PREPROCESSOR_DEFINITIONS = "NDEBUG=1";
+				GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_LABEL = YES;
+				GCC_WARN_UNUSED_PARAMETER = YES;
+				GCC_WARN_UNUSED_VALUE = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				 
+				PREBINDING = NO;
+				
+				WARNING_CFLAGS = "-Wall";
+			};
+			name = Deployment;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		E4D1EB6E08E0720400F1800B /* Build configuration list for PBXNativeTarget "LoginItemsAETest-compat" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E4D1EB6F08E0720400F1800B /* Development */,
+				E4D1EB7008E0720400F1800B /* Deployment */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Development;
+		};
+		E4FBB74608E03B2500B1AAFA /* Build configuration list for PBXNativeTarget "LoginItemsAETest" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E4FBB74708E03B2500B1AAFA /* Development */,
+				E4FBB74808E03B2500B1AAFA /* Deployment */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Development;
+		};
+		E4FBB74A08E03B2500B1AAFA /* Build configuration list for PBXProject "LoginItemsAETest" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E4FBB74B08E03B2500B1AAFA /* Development */,
+				E4FBB74C08E03B2500B1AAFA /* Deployment */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Development;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 20286C28FDCF999611CA2CEA /* Project object */;
+}
diff --git a/Read Me About LoginItemsAE.txt b/Read Me About LoginItemsAE.txt
new file mode 100644
index 0000000..2f320a3
--- /dev/null
+++ b/Read Me About LoginItemsAE.txt
@@ -0,0 +1,113 @@
+Read Me About LoginItemsAE
+==========================
+1.0
+
+This sample shows how to manipulate the list of login items, that is, the items that are launched when you log in [1].  The sample operates by sending Apple events to the "System Events" process, which has an AppleScript dictionary for manipulating login items.  The sample allows you to:
+
+o get a list of login items
+o add a login item to the end of the list
+o remove a login item
+
+The sample is structured as a GUI test application with a core library of reusable code that you can copy into your application.
+
+This sample requires Mac OS X 10.2 or higher, which is when the scriptable login items were introduced.
+
+This sample is a standalone replacement for MoreLoginItems, part of MoreIsBetter.  On the other hand, it is in no way related to the LoginItemsAPI code that has been distributed by DTS on a one-to-one basis in the past.
+
+[1] On Mac OS X 10.2.x and earlier systems, these are called "login items" and edited via the Login Items panel of System Preferences.  On Mac OS X 10.3 they changed to "startup items", edited in the Startup Items tab of the Accounts panel of System Preferences.  On Mac OS X 10.4, the name has changed back to "login items", but they're still edited in the Accounts panel.
+
+Packing List
+------------
+The sample contains the following items:
+
+o Read Me About LoginItemsAE.txt -- This file.
+
+o LoginItemsAE.h -- Header for the reusable library.
+
+o LoginItemsAE.c -- Implementation of the reusable library.
+
+o LoginItemsAETest -- A folder containing a test program that demonstrates the library.  The following items are within that folder:
+
+o LoginItemsAETest.xcodeproj -- An Xcode 2.1 project for the test program.
+
+o build -- A folder of pre-built binaries.
+
+o LoginItemsAETest.c -- Source for the test program
+
+o LoginItemsAETest.nib -- The user interface for the test program.
+
+o Info.plist -- A property list file for the test program.
+
+o LoginItemsAETest.pch -- A prefix file for the test program.
+
+Using the Sample
+----------------
+To use the sample, simply double click "LoginItemsAETest" within the "build" folder.  This displays a list of the current login items.  You can click the Add button to add a login item, the Remove button to remove one, and the Refresh button to refresh the list (see the Caveats section below).
+
+Building the Sample
+-------------------
+The sample was built using Xcode 2.1 on Mac OS X 10.4.  You should be able to just open the project, select the "LoginItemsAETest" target, and choose Build from the Build menu.  This will build the LoginItemsAETest application in the "Build" directory.  This is a universal binary that requires Mac OS X 10.4 or later.
+
+There is also a "LoginItemsAETest-compat" target that builds a PowerPC-only version that works all the way back to Mac OS X 10.2.x.
+
+How it Works
+------------
+The core reusable library works by sending Apple events to the "System Events" process.  These Apple events exactly mirror the events that are sent when you execute AppleScripts like:
+
+tell application "System Events"
+    make new login item at end 
+        with properties {path:"/Applications/TextEdit.app", hidden:false}
+end tell
+
+tell application "System Events"
+    properties of every login item
+end tell
+
+tell application "System Events"
+    delete login item 1
+end tell
+
+It is perfectly reasonable to not use the C code from this sample, but instead embed these AppleScripts in your program and run them using OSAExecuteEvent.  See DTS Q&A 1111 "Calling an AppleScript and providing parameters from an Application" for an example of how to do that.
+
+  <http://developer.apple.com/qa/qa2001/qa1111.html>
+
+Caveats
+-------
+In the GUI test application, the list of login items only updates if it was changed by the test application itself.  If other applications (for example, System Preferences) change the list of login items, the test application's UI does not update to reflect those changes.
+
+There are ways to hack around this problem (for example, polling the list), but they have all have drawbacks (for example, relying on information that Apple considers to be private).
+
+Under Mac OS X 10.2.x, the Login Items panel in System Preferences does not change as you modify the list using LoginItemsAE (or indeed using AppleScript).  This is a bug in Mac OS X 10.2.x that was fixed in Mac OS X 10.3.
+
+Due to a limitation of AESendMessage on Mac OS X 10.2.x, you should only call more LoginItemsAE routines from the main thread.  This isn't a problem on Mac OS X 10.3 and later.  You can work around this restriction by modifying LoginItemsAE' call to AESendMessage using the technique documented in Technote 2053.
+
+<http://developer.apple.com/technotes/tn2002/tn2053.html#TN001208>
+
+Due to a bug <rdar://problem/4052117> in Mac OS X 10.4 and later (up to including 10.4.2, which is the latest release at the time this was written), LoginItemsAE can't handle hidden login items properly.  That is, the value of kLIAEHidden is always false and you can't add a hidden login item.  This bug does not affect earlier systems.
+
+Credits and Version History
+---------------------------
+If you find any problems with this sample, mail <dts@apple.com> and I'll try to fix them up.
+
+1.0 (Sep 2005) was the first shipping version of this sample.  It is based on MoreLoginItems 1.0.2.
+
+Share and Enjoy.
+
+Apple Developer Technical Support
+Networking, Communications, Hardware
+
+20 Sep 2005
+
+$Log: Read\040Me\040About\040LoginItemsAE.txt,v $
+Revision 1.4  2005/09/28 19:59:59         
+Added comments describing each of the targets.
+
+Revision 1.3  2005/09/28 13:57:46         
+Change yet another grammo.
+
+Revision 1.2  2005/09/28 10:23:34         
+Fixed some grammos.
+
+Revision 1.1  2005/09/27 12:29:41         
+First checked in.
+