/* 
NSWorkspace.m
 
 Description...
 
 Copyright (C) 1996 Free Software Foundation, Inc.
 
 Author:	Scott Christley <scottc@net-community.com>
 Date:	1996
 Author:  Richard Frith-Macdonald <richard@brainstorm.co.uk>
 Date:	1998
 Author:  Felipe A. Rodriguez <far@pcmagic.net>
 Date:	Feb 1999
 Author:  H. Nikolaus Schaller <hns@computer.org>
 Date:	Aug 2003-2005
	- distributed Workspace support (dws)
	- findApplications made looking for MacOS X like bundles and document types (QSLaunchServices)
 
 This file is part of the mySTEP Library and is provided
 under the terms of the GNU Library General Public License.
 */

#import <Foundation/NSDictionary.h>
#import <Foundation/NSBundle.h>
#import <Foundation/NSData.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSTask.h>
#import <Foundation/NSException.h>
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSPathUtilities.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSConnection.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSDistantObject.h>
#import <Foundation/NSAttributedString.h>
#import <Foundation/NSURL.h>
#import <Foundation/NSPropertyList.h>
#import <Foundation/NSDistantObject.h>

#import <AppKit/NSWorkspace.h>
#import <AppKit/NSImage.h>
#import <AppKit/NSApplication.h>
#import <AppKit/NSPanel.h>

#import "NSAppKitPrivate.h"
#import "NSUIServer.h"

#include <sys/types.h>
#include <unistd.h>		// getpid()

#define WORKSPACE(notif_name)	NSWorkspace##notif_name##Notification

@interface GSListener : NSObject
+ (GSListener*) listener;
@end

@interface QSLaunchServices : NSObject
{
	NSMutableDictionary *QSApplicationIdentsByName;		// single ident for application name
	NSMutableDictionary *QSApplicationPathsByIdent;		// array of bundle paths for application identifier
	NSMutableDictionary *QSApplicationsByExtension;		// record list by extension
	NSMutableDictionary *QSFilePackageExtensions;		// extensions for file bundles
}

// .ext -> list of idents sorted by preferred app first
// name -> ident -> path
// ident -> list of paths sorted by most recent path first

+ (QSLaunchServices *) sharedLaunchServices;
- (id) init;
- (void) mapApplicationName:(NSString *) name toIdent:(NSString *) path;
- (void) mapApplicationIdent:(NSString *) ident toPath:(NSString *) path;
- (void) mapExtension:(NSString *) ext toIdent:(NSString *) ident withIconPath:(NSString *) path andRole:(NSString *) role andType:(NSString *) type;
- (NSArray *) identsForExtension:(NSString *) ext;	// returns NSDict entries (with CFBundleIdentifier records)
- (NSDictionary *) preferredIdentForExtension:(NSString *) ext;	// return preferred entry
- (void) setPreferredIdent:(NSString *) ident forExtension:(NSString *) ext;	// change preferred entry (Editor record first)
- (NSString *) absolutePathForAppBundleWithIdentifier:(NSString *) ident;	// map ident -> path (most recent)
- (NSString *) fullPathForApplication:(NSString *)appName;	// look up by name (most recent)
- (NSString *) applicationNameForIdent:(NSString *) ident;
- (void) treatExtensionAsFilePackage:(NSString *) ext;
- (BOOL) isFilePackageAtPath:(NSString *) path;
- (void) findApplicationsInDirectory:(NSString *) path;
- (void) clearCache;
- (void) findApplications;
- (void) writeDatabase;
- (NSDictionary *) applicationList;
- (NSDictionary *) fileTypeList;
- (NSArray *) knownApplications;

@end

// Class variables
static NSWorkspace *__sharedWorkspace = nil;
static QSLaunchServices *__launchServices = nil;
static BOOL __userDefaultsChanged = NO;
static BOOL __fileSystemChanged = NO;

@implementation QSLaunchServices

#define APPDATABASE [@"~/Library/Caches/com.quantum-step.mySTEP.QSLaunchServices.plist" stringByExpandingTildeInPath]

+ (QSLaunchServices *) sharedLaunchServices;
{
	if(!__launchServices)
		__launchServices=[self new];
	return __launchServices;
}

// - (void) dealloc;	// never

- (id) init;
{
	self=[super init];
	if(self)
		{ // load database (if possible)
		NSData *data=[NSData dataWithContentsOfFile:APPDATABASE];
		NSString *error;
		NSPropertyListFormat format;
		NSDictionary *defaults=[NSPropertyListSerialization propertyListFromData:data
												   mutabilityOption:NSPropertyListMutableContainers 
															 format:&format
												   errorDescription:&error];
#if 0
		NSLog(@"LS database=%@", defaults);
#endif
		if(data && !defaults)
			NSLog(@"QSLaunchServices did not load %@ due to: %@", APPDATABASE, error);
		QSApplicationIdentsByName = [[defaults objectForKey:@"QSApplicationIdentsByName"] retain];
		QSApplicationPathsByIdent = [[defaults objectForKey:@"QSApplicationPathsByIdent"] retain];
		QSApplicationsByExtension = [[defaults objectForKey:@"QSApplicationsByExtension"] retain];
		QSFilePackageExtensions = [[defaults objectForKey:@"QSFilePackageExtensions"] retain];
		}
	return self;
}

- (void) mapApplicationName:(NSString *) name toIdent:(NSString *) ident;
{ // add application name
#if 0
	NSLog(@"map Name %@ to %@", name, ident);
#endif
	// there is only one record per name - so contradicting duplicates will result in random entries
	if(name && ident)
		{
		if(!QSApplicationIdentsByName)
			[self findApplications];	// is missing - scan for applications
		[QSApplicationIdentsByName setObject:ident forKey:name];
		}
}

- (void) mapApplicationIdent:(NSString *) ident toPath:(NSString *) path;
{ // add application identifier
	NSMutableArray *records;
#if 0
	NSLog(@"map Ident %@ to %@", ident, path);
#endif
	if(!ident)
		return;
	if(!QSApplicationPathsByIdent)
		[self findApplications];	// is missing - scan for applications
	records=[QSApplicationPathsByIdent objectForKey:ident];
	if(!records)
		[QSApplicationPathsByIdent setObject:records=[NSMutableArray arrayWithCapacity:3] forKey:ident];
	if([records containsObject:path])
		return;	// already known
	// should insert sort so that the most recent executable is coming first
	[records addObject:path];
}

- (void) mapExtension:(NSString *) ext toIdent:(NSString *) ident withIconPath:(NSString *) path andRole:(NSString *) role andType:(NSString *) type;
{ // use @"schema:" for ext to store URL schemes
	NSMutableArray *records;
	NSDictionary *dict;
#if 0
	NSLog(@"map Extension %@ to Ident %@ icon %@ role %@ type %@", ext, ident, path, role, type);
#endif
	if(!ext || !ident || !role || !type)
		return;	// any important part is missing (icon path may be nil)
	if(!QSApplicationsByExtension)
		[self findApplications];	// is missing - scan for applications
	records=[QSApplicationsByExtension objectForKey:ext];
	if(!records)
		[QSApplicationsByExtension setObject:records=[NSMutableArray arrayWithCapacity:3] forKey:ext];
	// go through records and check if entry with ident, role, type is already stored
	dict=[NSDictionary dictionaryWithObjectsAndKeys:
		ident, @"CFBundleIdentifier",
		role, @"CFBundleTypeRole",
		type, @"CFBundleTypeName",
		path, @"CFBundleTypeIconPath",	// path may be nil, so this must be the last entry
		nil];
	[records addObject:dict];	// add record
}

- (NSArray *) identsForExtension:(NSString *) ext;
{ // return list of application records for this extension
	// returns all records with matching extension
	// sorted by preferred first and descending by Editor, Viewer, None - should be sorted when generating database
	if(!QSApplicationsByExtension)
		[self findApplications];	// is missing - scan for applications
	return [QSApplicationsByExtension objectForKey:ext];
}

- (NSDictionary *) preferredIdentForExtension:(NSString *) ext;
{ // find preferred ident for this extension
	NSArray *a=[self identsForExtension:ext];
#if 0
	NSLog(@"identsForExtension %@: %@\n%@", ext, a, QSApplicationsByExtension);
#endif
	if([a count] == 0)
		return nil;
	return [a objectAtIndex:0];	// first, i.e. preferred
}

- (void) setPreferredIdent:(NSString *) ident forExtension:(NSString *) ext;
{ // allow user interface to change preferred application
	if(!QSApplicationsByExtension)
		[self findApplications];	// is missing - scan for applications
	// move to front
	[self writeDatabase];
}

- (NSString *) absolutePathForAppBundleWithIdentifier:(NSString *) ident;
{ // find most recent version with this identifier
	NSArray *a;
#if 0
	NSLog(@"ident->path: %@", ident);
#endif
	if(!QSApplicationPathsByIdent)
		[self findApplications];	// is missing - scan for applications
	a=[QSApplicationPathsByIdent objectForKey:ident];
	if([a count] == 0)
		return nil;
	return [a objectAtIndex:0];	// first, i.e. preferred
}

- (NSString *) fullPathForApplication:(NSString *)appName
{
	NSString *last=[appName lastPathComponent];
//	NSString *ext=[appName pathExtension];
	NSString *path;
	if(!appName)
		return nil;		// unspecified
	if([appName isAbsolutePath])
		return appName; // is already a full path
	if(![appName isEqual:last])
		return nil;		// has path components, i.e. not a plain app name - don't treat as relative name
	appName=[appName stringByDeletingPathExtension]; // remove extension (if present)
#if 0
	NSLog(@"fullPathForApplication:%@", appName);
	NSLog(@"QSApplicationIdentsByName:%@", QSApplicationIdentsByName);
#endif
	if(!QSApplicationIdentsByName)
		{ // first call
		[self findApplications];	// build database
		return [self absolutePathForAppBundleWithIdentifier:[QSApplicationIdentsByName objectForKey:appName]];	// try to map name to ident to path
		}
	path=[self absolutePathForAppBundleWithIdentifier:[QSApplicationIdentsByName objectForKey:appName]];	// try to map name to ident to path
	if(!path)
		[self findApplications];	// did not find -> rebuild database
	return [self absolutePathForAppBundleWithIdentifier:[QSApplicationIdentsByName objectForKey:appName]];	// try again - returns nil if still unknown
}

- (NSString *) applicationNameForIdent:(NSString *) ident;
{ // look up application name for bundle identifier (randomly choosen if different bundles have same ident)
	NSArray *a;
	if(!QSApplicationIdentsByName)
		[self findApplications];	// is missing - scan for applications
	a=[QSApplicationIdentsByName allKeysForObject:ident];
	if([a count] == 0)
		return nil;	// not found
	return [a objectAtIndex:0];	// first, i.e. preferred
}

- (void) treatExtensionAsFilePackage:(NSString *) ext;
{ // treat as file package
	if(ext)
		[QSFilePackageExtensions setObject:self forKey:ext];	// add to list
}

- (BOOL) isFilePackageAtPath:(NSString *) path
{ // check if it is a file package
	NSFileManager *fm=[NSFileManager defaultManager];
    BOOL isDir = NO;
    BOOL exists = [fm fileExistsAtPath:path isDirectory:&isDir];
    if(!(exists && isDir))
		return NO;	// must be a directory
	if([QSFilePackageExtensions objectForKey:[path pathExtension]])
		return YES;	// file extension is in list of Document bundles
	// otherwise we must have a subdirectory named "Contents"
	path=[path stringByAppendingPathComponent:@"Contents"];
	exists = [fm fileExistsAtPath:path isDirectory:&isDir];
    if(!(exists && isDir))
		return NO;
	// and we must have an Info.plist file
	exists = [fm fileExistsAtPath:[path stringByAppendingPathComponent:@"Info.plist"] isDirectory:&isDir];
    return ((exists && !isDir));
}

- (void) addApplicationAtPath:(NSString *) path;
{ // analyse application bundle to store in database
	NSBundle *b;
	NSDictionary *info;
	NSArray *filetypes;
	NSArray *urltypes;
	NSString *appname;
	NSString *ident;
	NSDictionary *ft;
	NSEnumerator *fte;
	b=[NSBundle bundleWithPath:path];	// open as bundle
	// check type - should have CFBundlePackageType="APPL" bundle
	info=[b infoDictionary];
	if(!info)
		{
#if 0
		NSLog(@"can't load Info.plist for %@", path);
#endif
		return;
		}
	appname=[info objectForKey:@"CFBundleName"];
	if(!appname)
		appname=[[path lastPathComponent] stringByDeletingPathExtension];	// if no display name is defined
	// should check if already defined - keep/sort newer one
	ident=[info objectForKey:@"CFBundleIdentifier"];
	if(!ident)
		{
#if 0
		NSLog(@"Missing CFBundleIdentifier for %@", path);
#endif
		return;
		}
	if(![[NSFileManager defaultManager] isExecutableFileAtPath:[b executablePath]])
		{
		// FIXME: Here, we could call something like '/usr/bin/softpear $BUNDLE/Contents/MacOS/$EXECUTABLE $*' 
#if 0
		NSLog(@"No executable at %@", [b executablePath]);
#endif
		return;	// not an executable at path
		}
	[self mapApplicationName:appname toIdent:ident];	// map name to identifier
	[self mapApplicationIdent:ident toPath:path];		// map identifier to path
	filetypes=[info objectForKey:@"CFBundleDocumentTypes"];
	urltypes=[info objectForKey:@"CFBundleURLTypes"];
	if(!filetypes && urltypes)
		filetypes=urltypes;	// URL types only
	else if(filetypes && urltypes)
		filetypes=[filetypes arrayByAddingObjectsFromArray:urltypes];	// merge both
	if(!filetypes)
		{
#if 0
		NSLog(@"No CFBundleDocumentTypes for %@", path);
#endif
		return;
		}
	fte=[filetypes objectEnumerator];
	while((ft=[fte nextObject]))
		{ // process file types
		NSString *iconFile;
		NSString *iconPath;
		NSString *role;
		NSString *type;
		NSEnumerator *ftex;
		NSString *ext;
		iconFile=[ft objectForKey:@"CFBundleTypeIconFile"];
		if(!iconFile)
			iconFile=[ft objectForKey:@"CFBundleURLIconFile"];
		iconPath=[iconFile length]>0?[b pathForResource:iconFile ofType:nil inDirectory:nil]:nil;	// try to look up in application bundle
		role=[ft objectForKey:@"CFBundleTypeRole"];
		type=[ft objectForKey:@"CFBundleTypeName"];
		if(!type)
			type=[ft objectForKey:@"CFBundleURLName"];
		ftex=[[ft objectForKey:@"CFBundleTypeExtensions"] objectEnumerator];
		while((ext=[ftex nextObject]))
			{ // process all potential file extensions for this type (e.g. .jpg, .jpeg, .tif, .tiff, ...)
			[self mapExtension:ext toIdent:ident withIconPath:iconPath andRole:role andType:type];
			}
		ftex=[[ft objectForKey:@"CFBundleURLSchemes"] objectEnumerator];
		while((ext=[ftex nextObject]))
			{ // process all potential URL schemes for this type (e.g. http, ftp, file, folder, ...)
			[self mapExtension:[ext stringByAppendingString:@":"] toIdent:ident withIconPath:iconPath andRole:role andType:type];
			}
		if([[ft objectForKey:@"LSTypeIsPackage"] boolValue])
			[self treatExtensionAsFilePackage:ext];
		}
	// might call findApplicationsInDirectory:Resources to get embedded .apps
}

- (void) findApplicationsInDirectory:(NSString *) path
{ // add all applications found below directory (doing a deep search)
	NSString *fp=[path stringByExpandingTildeInPath];
	NSDirectoryEnumerator *de=[[NSFileManager defaultManager] enumeratorAtPath:fp];
	NSString *dp;
#if 1
	NSLog(@"findApplications in %@", fp);
#endif
	while((dp=[de nextObject]))
		{ // all files in search path with .app extension
		NSAutoreleasePool *arp;
		// we should try to skip non-app bundles!
		if(![[dp pathExtension] isEqualToString:@"app"])
			continue;
		arp=[NSAutoreleasePool new];
		[de skipDescendents];		// don't search/find embedded applications! (why or why not?)
		dp=[fp stringByAppendingPathComponent:dp];	// make absolute path
#if 1
		NSLog(@"candidate: %@", dp);
#endif
		[self addApplicationAtPath:dp];
		[arp release];
		}
}

- (void) clearCache;
{
	[QSApplicationIdentsByName release];	// clear database
	QSApplicationIdentsByName=nil;
	[QSApplicationPathsByIdent release];
	QSApplicationPathsByIdent=nil;
	[QSApplicationsByExtension release];
	QSApplicationsByExtension=nil;
	[QSFilePackageExtensions release];
	QSFilePackageExtensions=nil;
}

- (void) findApplications
{
	NSAutoreleasePool *arp=[NSAutoreleasePool new];
	NSArray *path=[[NSBundle bundleForClass:[self class]] objectForInfoDictionaryKey:@"NSApplicationSearchPath"];
	NSEnumerator *p=[path objectEnumerator];
	NSString *dir;
#if 1
	NSLog(@"findApplications");
#endif
	[self clearCache];
	QSApplicationIdentsByName=[[NSMutableDictionary dictionaryWithCapacity:50] retain];
	QSApplicationPathsByIdent=[[NSMutableDictionary dictionaryWithCapacity:50] retain];
	QSApplicationsByExtension=[[NSMutableDictionary dictionaryWithCapacity:100] retain];
	QSFilePackageExtensions=[[NSMutableDictionary dictionaryWithCapacity:20] retain];
	while((dir=[p nextObject]))
		[self findApplicationsInDirectory:dir];	// fill with applications
	[self writeDatabase];
	[arp release];
}

- (void) writeDatabase;
{
	NSDictionary *dict;
	NSData *data;
	NSString *error;
	dict=[NSDictionary dictionaryWithObjectsAndKeys:
		QSApplicationIdentsByName, @"QSApplicationIdentsByName",
		QSApplicationPathsByIdent, @"QSApplicationPathsByIdent",
		QSApplicationsByExtension, @"QSApplicationsByExtension",
		QSFilePackageExtensions, @"QSFilePackageExtensions",
		nil
		];
#if 1
	NSLog(@"writeDatabase");
#endif
	data=[NSPropertyListSerialization dataFromPropertyList:dict
#if 0	// human readable for testing */
				format:NSPropertyListXMLFormat_v1_0
#else
				format:NSPropertyListBinaryFormat_v1_0
#endif
				errorDescription:&error];
	if(!data)
		NSLog(@"Could not create Launch Services database from %@", dict);
	else if(![data writeToFile:APPDATABASE atomically:YES])
		NSLog(@"Could not save Launch Services database to %@", APPDATABASE);
#if 1
	NSLog(@"done");
#endif
}

- (NSDictionary *) applicationList; { if(!QSApplicationIdentsByName) [self findApplications]; return QSApplicationIdentsByName; }
- (NSDictionary *) fileTypeList; { if(!QSApplicationsByExtension) [self findApplications]; return QSApplicationsByExtension; }
- (NSArray *) knownApplications; { if(!QSApplicationIdentsByName) [self findApplications]; return [QSApplicationIdentsByName allKeys]; }

@end

@implementation	NSWorkspace

+ (void) initialize
{
	if (!__sharedWorkspace)
		__sharedWorkspace = (NSWorkspace*)NSAllocateObject(self, 0, NSDefaultMallocZone());
}

+ (NSWorkspace *) sharedWorkspace				{ return __sharedWorkspace; }
+ (id) alloc									{ return NIMP; }
- (void) dealloc								{ NIMP; [super dealloc]; }
- (id) init										{ return NIMP; }

- (NSNotificationCenter *) notificationCenter;
{ // where workspace notifications originate
	return [NSNotificationCenter defaultCenter];
}

	// 
	// this is the most generic file launcher on which all other -open methods are based on
	//

- (BOOL) openURLs:(NSArray *) list		// may be nil to denote application launch without files
			withAppBundleIdentifier:(NSString *) identOrApp		// may be absolute path, appName, bundleIdentifier or nil - .app may be present or omitted
							options:(NSWorkspaceLaunchOptions) options
	 additionalEventParamDescriptor:(id) ignored		// we have no NSAppleEventDescriptor
				  launchIdentifiers:(NSArray **) identifiers;
{
	NSMutableDictionary *apps;
	NSEnumerator *e;
#if 0
	NSLog(@"NSWorkspace: openURLs: '%@'", list);
	NSLog(@"  withAppBundleIdentifier '%@'", identOrApp);
#endif
	if(!identOrApp)
		{ // get application(s) for scheme(s) or file extension(s) in list
		NSURL *arg;
		if([list count] == 0)
			return NO;	// nothing to open with no application...
		apps=[NSMutableDictionary dictionaryWithCapacity:5];
		e=[list objectEnumerator];	// go through all arguments
		while((arg=[e nextObject]))
			{
			NSMutableArray *arglist;	// for current ident
			NSString *ext;
			if([arg isFileURL])
				{ // is file - try to find by extension
				NSString *path=[arg path];
				ext=[path pathExtension];
#if 0
				NSLog(@"  path=%@", path);
				NSLog(@"  ext=%@", ext);
#endif
				if([path isAbsolutePath] && [ext isEqualToString:@"app"] && [list count] == 1)
					{ // open single application (well, myFinder could handle this if it is asked to open)
					apps=[NSDictionary dictionaryWithObject:[NSArray array] forKey:path];
					break;
					}
				else if([ext length] == 0)
					{ // check for directory and process extension "/"
					NSFileManager *fm=[NSFileManager defaultManager];
					BOOL isDir=NO;
#if 0
					NSLog(@"check for directory at %@", path);
#endif
					if([fm fileExistsAtPath:path isDirectory:&isDir] && isDir)
						ext=@"/";
					}
				}
			else
				ext=[[arg scheme] stringByAppendingString:@":"];	// include : for lookup by URL scheme
			if(ext)
				{
#if 0
				NSLog(@"  try extension: %@", ext);
#endif
				if(!__launchServices)
					[QSLaunchServices sharedLaunchServices];
				identOrApp=[[__launchServices preferredIdentForExtension:ext] objectForKey:@"CFBundleIdentifier"];
				if(!identOrApp)
					identOrApp=[[__launchServices preferredIdentForExtension:@"*"] objectForKey:@"CFBundleIdentifier"];
				}
			if(!identOrApp)
				return NO;	// don't know how to launch
			arglist=[apps objectForKey:identOrApp];
			if(!arglist)
				[apps setObject:arglist=[NSMutableArray arrayWithCapacity:10] forKey:identOrApp];
			[arglist addObject:arg];	// make that app open this url
			}
		}
	else
		apps=[NSDictionary dictionaryWithObject:list forKey:identOrApp];
#if 0
	NSLog(@"openURLs -> %@", apps);
#endif
	if(identifiers)
		*identifiers=[NSMutableArray arrayWithCapacity:[apps count]];
	e=[apps keyEnumerator];
	while((identOrApp=[e nextObject]))
		{ // launch all applications needed for these URLs
		NSNumber *ident=nil;
		if(![self launchAppWithBundleIdentifier:identOrApp options:options additionalEventParamDescriptor:[apps objectForKey:identOrApp] launchIdentifier:&ident])
			return NO;
		if(identifiers && ident)
			[(NSMutableArray *) *identifiers addObject:ident];	// add to list
		}
	return YES;
}

- (BOOL) openURL:(NSURL *) url;
{
	return [self openURLs:[NSArray arrayWithObject:url]
				 withAppBundleIdentifier:nil
				 options:NSWorkspaceLaunchDefault 
				 additionalEventParamDescriptor:nil
				 launchIdentifiers:NULL];
}

- (BOOL) openFiles:(NSArray *) paths	// may be nil to open application
   withApplication:(NSString *) appName	// may be absolute path, appName, bundleIdentifier or nil - .app may be present or omitted
	 andDeactivate:(BOOL) flag
{
	NSMutableArray *a;
	if(paths)
		{ // translate paths into file URLs
		NSEnumerator *e=[paths objectEnumerator];
		NSString *path;
		a=[NSMutableArray arrayWithCapacity:[paths count]];
		while((path=[e nextObject]))
			{
			if(![path isAbsolutePath])
				{
				NSLog(@"NSWorkspace openFiles requires absolute paths (%@)", path);
				return NO;
				}
			[a addObject:[NSURL fileURLWithPath:path]];
			}
		}
	else
		a=nil;
	return			[self openURLs:a
		   withAppBundleIdentifier:appName
						   options:flag?(NSWorkspaceLaunchDefault|NSWorkspaceLaunchWithoutActivation):NSWorkspaceLaunchDefault
	additionalEventParamDescriptor:nil		// we have no NSAppleEventDescriptor
				 launchIdentifiers:NULL];
}

- (BOOL) openFile:(NSString *) fullPath
  withApplication:(NSString *) appName	// may be full path or partially or nil - .app may be present or omitted
	andDeactivate:(BOOL) flag
{
	return [self openFiles:fullPath?[NSArray arrayWithObject:fullPath]:nil withApplication:appName andDeactivate:flag];
}

- (BOOL) openFile:(NSString *) fullPath withApplication:(NSString *) appName
{ // and deactivate
	return [self openFile:fullPath withApplication:appName andDeactivate:YES];
}

- (BOOL) openFile:(NSString *) fullPath
{ // default application
	return [self openFile:fullPath withApplication:nil andDeactivate:YES];
}

- (BOOL) openTempFile:(NSString *) fullPath
{ // open new but not adding to recents...
	return
	[self openURLs:[NSArray arrayWithObject:[NSURL fileURLWithPath:fullPath]]
		   withAppBundleIdentifier:nil
		   options:NSWorkspaceLaunchDefault | NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchNewInstance
	additionalEventParamDescriptor:nil
 launchIdentifiers:NULL];
}

- (BOOL) openFile:(NSString *) fullPath
		fromImage:(NSImage *) anImage
			   at:(NSPoint) point
		   inView:(NSView *) aView
{
	// somehow pass anImage, point and view to myFinder - and make it not process the file
	return [self openFile:fullPath withApplication:@"myFinder"];
}

- (BOOL) selectFile:(NSString *) fullPath
		 inFileViewerRootedAtPath:(NSString *) rootFullpath
{
	// somehow pass rootFullpath to myFinder and make it 'show' instead of 'open'
	return [self openFile:fullPath withApplication:@"myFinder"];
}

- (BOOL) launchApplication:(NSString *) appName
{
	return [self launchApplication:appName showIcon:YES autolaunch:NO];
}

- (BOOL) launchApplication:(NSString *) appName
				  showIcon:(BOOL) showIcon
				autolaunch:(BOOL) autolaunch
{
	appName=[self fullPathForApplication:appName];
	if(!appName)
		return NO;	// unknown application
	// FIXME: how to handle showIcon and autolaunch?
	return [self launchAppWithBundleIdentifier:appName options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifier:NULL];
}

- (BOOL) launchAppWithBundleIdentifier:(NSString *) identOrApp
							   options:(NSWorkspaceLaunchOptions) options
		additionalEventParamDescriptor:(id) ignored
					  launchIdentifier:(NSNumber **) identifiers;
{ // launch application (without files)
	NSString *path;
	NSBundle *b;
	NSDictionary *dict;
	id a;
#if 1
	NSLog(@"launchAppWithBundleIdentifier: %@ options: %d eventparam: %@", identOrApp, options, ignored); 
#endif
	if (!__launchServices)
		[QSLaunchServices sharedLaunchServices];
	path=[__launchServices absolutePathForAppBundleWithIdentifier:identOrApp];	// returns nil if not found
	if(!path)
		path=[__launchServices fullPathForApplication:identOrApp];	// returns nil if not found - should be application name or absolute path
#if 0
	NSLog(@"  resolved application path: %@", path);
#endif
	if(!path)
		return NO;	// still not found
	b=[NSBundle bundleWithPath:path];

	// FIXME: somewhere here we should move this code into __launchServices

	if(!b)
		return NO;	// is not a valid bundle
	if(options&NSWorkspaceLaunchInhibitingBackgroundOnly)
		{ // check if we want to launch a background only application and deny
		if([[b objectForInfoDictionaryKey:@"LSBackgroundOnly"] boolValue])
			return NO;	// is background only
		}
	if(options&NSWorkspaceLaunchNewInstance)
		a=nil;	// force to launch new instance
	else
		{
		if([[b bundleIdentifier] isEqual:[[NSBundle mainBundle] bundleIdentifier]])
			{
			a=[GSListener listener];	// handle like a remote connection
										// a=[NSApp delegate];	// that is myself
			}
		else
			{ // Try to contact an existing instance
			NS_DURING
				a = [NSConnection connectionWithRegisteredName:[b bundleIdentifier] host:@""];
				if(a)
					{ // ports exists (but might not respond
					[a setRequestTimeout:1.0];	// should answer nearly immediately
					[a setReplyTimeout:2.0];
					a = [a rootProxy];
					}
			NS_HANDLER
				a = nil;										// Fatal error in DO
			NS_ENDHANDLER
			}
		}
	dict=[NSDictionary dictionaryWithObjectsAndKeys:
		path, @"NSApplicationPath",
		[b objectForInfoDictionaryKey:@"CFBundleName"], @"NSApplicationName",
		[b bundleIdentifier], @"NSApplicationBundleIdentifier",
		[NSNumber numberWithInt:0], @"NSApplicationProcessIdentifier",
		[NSNumber numberWithInt:0], @"NSApplicationProcessSerialNumberHigh",
		[NSNumber numberWithInt:0], @"NSApplicationProcessSerialNumberLow",
		nil];
	[[self notificationCenter] postNotificationName:WORKSPACE(WillLaunchApplication)
											 object:self
										   userInfo:dict];
	if(a == nil)
		{ // fatal in DO -> try to launch new process and pass URLs to open as argument(s)
		NSTimeInterval waitInterval;
		NSDate *date;
		NSString *executable=[b executablePath];	// get executable within bundle
		NSMutableArray *args;
		if(!executable)
			return NO;	// invalid bundle to launch
		args=[NSMutableArray arrayWithCapacity:15];
	//	[args addObject:[NSString stringWithFormat:@"-psn_%lu_%lu", high, low]];
		NS_DURING   // shield caller from exceptions
			if(ignored)
				{ // convert URLs to command line arguments
				NSEnumerator *e=[ignored objectEnumerator];
				NSURL *url;
				while((url=[e nextObject]))
					{
					if([url isFileURL])
						[args addObject:[url path]];	// plain path - might do a tilde abbreviation?
					else
						[args addObject:[url absoluteString]];
					}
				}
			if(options&NSWorkspaceLaunchAndPrint)
				[args addObject:@"-NSPrint"];	// append behind files or they would become arguments to the options
			if((options&(NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchNewInstance)) == (NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchNewInstance))
				[args addObject:@"-NSTemp"];
			else if(options&NSWorkspaceLaunchWithoutAddingToRecents)
				[args addObject:@"-NSNoUI"];
			else if(options&NSWorkspaceLaunchNewInstance)
				[args addObject:@"-NSNew"];
#if 0
			NSLog(@"NSWorkspace launchApplication: '%@' $*=%@", executable, args);
#endif
			// FIXME: set environment to [b objectForInfoDictionaryKey:@"LSEnvironment"]
			[NSTask launchedTaskWithLaunchPath:executable arguments:args];
			// update process ID and Serial number
		NS_HANDLER
			NSLog(@"could not launchApplication %@ due to %@", path, [localException reason]);
			return NO;  // did not launch - e.g. bad executable
		NS_ENDHANDLER
		if(!(options&NSWorkspaceLaunchAsync))
			{
#if 0
			NSLog(@"Wait until launched.");
#endif
			// could also wait until app appears in [NSApp launchedApplications] if that is available
			waitInterval=1.0;
			date=[NSDate dateWithTimeIntervalSinceNow:waitInterval];
			[NSTimer scheduledTimerWithTimeInterval:waitInterval invocation:nil repeats:NO];	// break runloop
			[[NSRunLoop currentRunLoop] runUntilDate:date];
			}
		}
	else
		{ // DO connection exists - just forward
#if 0
		NSLog(@"connection to application=%@", a);
#endif
		[[a retain] autorelease];	// FIXME: do we really need that???
									// if we are sending to ourselves, a is the delegate - and should respond to this method!
		if(![a _application:a openURLs:ignored withOptions:options])	// call the handler of GSListener
			return NO;
		}
	if(options&NSWorkspaceLaunchAndHideOthers)
		[NSApp hide:self];
	if(options&NSWorkspaceLaunchAndHide)
		[NSApp hideOtherApplications:self];
	if(options&NSWorkspaceLaunchWithoutActivation)
		[NSApp deactivate];
	// FIXME: this should probably be postponed until application did really launch and register with DWS
	[[self notificationCenter] postNotificationName:WORKSPACE(DidLaunchApplication)
											 object:self
										   userInfo:dict];
	return YES;
}

// internal for workspace file operations

- (void) _taskDidTerminate:(NSNotification *)aNotification
{
	NSTask *task = (NSTask *)[aNotification object];
	
	if ([task terminationStatus] == 0)
		{
		NSString *p = [task currentDirectoryPath];
		
		NSLog(@"workspace task ended normally");
		
		[self selectFile:p inFileViewerRootedAtPath:p];
		}
	else
		NSLog(@"workspace task did not end normally");
}

- (BOOL) performFileOperation:(NSString *)operation
					   source:(NSString *)source
				  destination:(NSString *)destination
						files:(NSArray *)files
						  tag:(int *)tag
{
	int result=NSAlertDefaultReturn, count = [files count];
	
	NSLog(@"performFileOperation %@", operation);
	
	if(operation == NSWorkspaceMoveOperation)			 
		{
		*tag = 0;
		while (count--)
			{
			NSString *f = [files objectAtIndex: count];
			NSString *s = [source stringByAppendingPathComponent:f];
			NSString *d = [destination stringByAppendingPathComponent:f];
			NSString *a = [[NSProcessInfo processInfo] processName];
			result = NSRunAlertPanel(a, @"Move: %@ to: %@?", @"Move", 
									 @"Cancel", NULL, s, d);
			if (result == NSAlertDefaultReturn && ![[NSFileManager defaultManager] movePath:s toPath:d handler:0])
				return NO;
			NSLog(@"not moving");
			}	}
	else if(operation == NSWorkspaceCopyOperation)
		{
		*tag = 1;
		while (count--)
			{
			NSString *f = [files objectAtIndex: count];
			NSString *s = [source stringByAppendingPathComponent:f];
			NSString *d = [destination stringByAppendingPathComponent:f];
			NSString *a = [[NSProcessInfo processInfo] processName];
			result = NSRunAlertPanel(a, @"Copy: %@ ?", @"Copy", 
									 @"Cancel", NULL, s);
			if (result == NSAlertDefaultReturn && ![[NSFileManager defaultManager] copyPath:s toPath:d handler:0])
				return NO;
			NSLog(@"not copying");
			}	}
	else if(operation == NSWorkspaceLinkOperation)
		{
		*tag = 2;
		while (count--)
			{
			NSString *f = [files objectAtIndex: count];
			NSString *s = [source stringByAppendingPathComponent:f];
			NSString *d = [destination stringByAppendingPathComponent:f];
			NSString *a = [[NSProcessInfo processInfo] processName];
			result = NSRunAlertPanel(a, @"Link: %@ ?", @"Link", 
									 @"Cancel", NULL, s);
			if (result == NSAlertDefaultReturn && ![[NSFileManager defaultManager] linkPath:s toPath:d handler:0])
				return NO;
			NSLog(@"not linking");
			}	}
	else if(operation == NSWorkspaceCompressOperation)	 *tag = 3;
	else if(operation == NSWorkspaceDecompressOperation)
		{
		NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
		
		*tag = 4;
		while (count--)
			{
			BOOL tar = NO;
			NSArray *args;
			NSString *p, *f = [files objectAtIndex: count];
			NSString *s = [source stringByAppendingPathComponent:f];
			NSString *tmp, *ext = [s pathExtension];
			NSTask *task;
			
			if ([ext isEqualToString: @"bz2"])
				p = @"/usr/bin/bunzip2";
			else if ([ext isEqualToString: @"gz"]
					 || [ext isEqualToString: @"Z"]
					 || [ext isEqualToString: @"z"])
				p = @"/bin/gunzip";
			else if ([ext isEqualToString: @"tgz"]
					 || [ext isEqualToString: @"taz"])
				{
				p = @"/bin/gunzip";
				tar = YES;
				}
			else
				continue;
			
			tmp = [NSString stringWithFormat:@"/tmp/%@.%d.workspace", f,
				(int)[NSDate timeIntervalSinceReferenceDate]];
			NSLog(@"create temporary directory %@", tmp);
			if(![[NSFileManager defaultManager] createDirectoryAtPath:tmp
														   attributes:nil])
				return NO;
			
			if (tar || [s rangeOfString: @".tar"].length > 0)
				s = [NSString stringWithFormat:@"%@ -c %@ | tar xfpv -", p, s];
			else
				s = [NSString stringWithFormat:@"%@ %@", p, s];
			args = [NSArray arrayWithObjects: @"-c", s, nil];
			
			NSLog(@"launching with str arg %@",s);
			task = [NSTask new];
			[task setCurrentDirectoryPath: tmp];
			[task setLaunchPath: @"/bin/sh"];
			[task setArguments: args];
			
			[nc addObserver: self
				   selector: @selector(_taskDidTerminate:)
					   name: NSTaskDidTerminateNotification
					 object: task];
			[task launch];
			}	}
	else if(operation == NSWorkspaceEncryptOperation)	 *tag = 5;
	else if(operation == NSWorkspaceDecryptOperation)	 *tag = 6;
	else if(operation == NSWorkspaceDestroyOperation)
		{
		*tag = 7;
		while (count--)
			{
			NSString *f = [files objectAtIndex: count];
			NSString *s = [source stringByAppendingPathComponent: f];
			NSString *a = [[NSProcessInfo processInfo] processName];
			
			result = NSRunAlertPanel(a, @"Destroy path: %@ ?", @"Destroy", 
									 @"Cancel", NULL, s);
			
			if (result == NSAlertDefaultReturn && ![[NSFileManager defaultManager] removeFileAtPath:s handler:0])
				return NO;
			NSLog(@"not deleting");
			}	}
	else if(operation == NSWorkspaceRecycleOperation)
		{
		*tag = 8;
		while (count--)
			{
			NSString *f = [files objectAtIndex: count];
			NSString *s = [source stringByAppendingPathComponent:f];
			NSString *d = [@"/.NextTrash" stringByAppendingPathComponent:f];
			NSString *a = [[NSProcessInfo processInfo] processName];
			result = NSRunAlertPanel(a, @"Recycle: %@ to: %@?", @"Recycle", 
									 @"Cancel", NULL, s, d);
			if (result == NSAlertDefaultReturn && [[NSFileManager defaultManager] movePath:s toPath:d handler:0])
				return NO;
			NSLog(@"not moving");
			}	}
	else if(operation == NSWorkspaceDuplicateOperation)
		{
		*tag = 9;
		while (count--)
			{
			NSString *f = [files objectAtIndex: count];
			NSString *n = [NSString stringWithFormat: @"CopyOf%@", f];
			NSString *s = [source stringByAppendingPathComponent:f];
			NSString *p = [source stringByAppendingPathComponent:n];
			
			if(![[NSFileManager defaultManager] copyPath:s toPath:p handler:0])
				return NO;
			}	}
	
	[[self notificationCenter] postNotificationName:WORKSPACE(DidPerformFileOperation) object:self];
	
	return (result == NSAlertDefaultReturn) ? YES : NO;
}

- (NSString *) absolutePathForAppBundleWithIdentifier:(NSString *) bundleIdentifier;
{ // look up
	if (!__launchServices)
		[QSLaunchServices sharedLaunchServices];
	return [__launchServices absolutePathForAppBundleWithIdentifier:bundleIdentifier];
}

- (NSString *) fullPathForApplication:(NSString *)appName
{
	if (!__launchServices)
		[QSLaunchServices sharedLaunchServices];
	return [__launchServices fullPathForApplication:appName];
}

- (BOOL) getFileSystemInfoForPath:(NSString *)fullPath
					  isRemovable:(BOOL *)removableFlag
					   isWritable:(BOOL *)writableFlag
					isUnmountable:(BOOL *)unmountableFlag
					  description:(NSString **)description
							 type:(NSString **)fileSystemType
{
	return NO;
}

- (BOOL) getInfoForFile:(NSString *)fullPath
			application:(NSString **)appName
				   type:(NSString **)type
{
	NSFileManager *fm = [NSFileManager defaultManager];
	NSDictionary *app;
	BOOL is_dir;
	NSString *dummy;
	NSString *dummy2;
#if 0
	NSLog(@"getInfoForFile: %@", fullPath);
#endif
	if(!type)
		type=&dummy;
	if(!appName)
		appName=&dummy2;
	if(!fullPath || ![fullPath isAbsolutePath])
		{
		NSLog(@"NSWorkspace getInfoForFile requires an absolute path (%@)", fullPath);
		return NO;
		}
	if(![fm fileExistsAtPath:fullPath isDirectory:&is_dir])
		return NO;  // does not exist
	if (!__launchServices)
		[QSLaunchServices sharedLaunchServices];	// initialize if needed
	if((app = [__launchServices preferredIdentForExtension:[fullPath pathExtension]]))
		{ // try to get get application name from file extensions database
#if 1
		NSLog(@"app for extension %@ = %@", [fullPath pathExtension], app);
#endif
		*type=[app objectForKey:@"CFBundleTypeName"];
		*appName=[__launchServices applicationNameForIdent:[app objectForKey:@"CFBundleIdentifier"]];	// application name
#if 0
		NSLog(@"ext=%@", [fullPath pathExtension]);
		NSLog(@"preferred app=%@", app);
		NSLog(@"appname=%@", appName);
#endif
		if([*type length] != 0 && ![[app objectForKey:@"CFBundleTypeRole"] isEqualToString:@"None"])
			return YES; // found - and role is not None (i.e. providing Icon only)
		}
	if(is_dir)
		{ // directoy
		if([[fullPath pathExtension] isEqualToString: @"app"])  // should better check to be a bundle of type 'APPL'
			{
			*appName=fullPath;  // launch applications by themselves
			*type=NSApplicationFileType;
			return YES;
			}
		*appName=@"myFinder";	// open directories by default
		*type=NSDirectoryFileType;
		return YES;
		}
	// check for executable -> NSShellCommandFileType
	// check for mounted directory -> NSFilesystemFileType
	*appName=@"myFinder";	// default
	*type=NSPlainFileType;
	return NO;  // unknown plain file - no idea how to open
}

- (BOOL) isFilePackageAtPath:(NSString *) path
{ // check if it is a file package
	if(!__launchServices)
		[QSLaunchServices sharedLaunchServices];
	return [__launchServices isFilePackageAtPath:path];
}

- (NSImage *) iconForFile:(NSString *)fullPath
{
	NSFileManager *fm = [NSFileManager defaultManager];
	NSImage *i=nil;
	NSBundle *b;
	NSString *icon;
	BOOL is_dir = NO;
#if 0
	NSLog(@"NSWorkspace iconForFile: %@", fullPath);
#endif
	if(!fullPath || ![fullPath isAbsolutePath])
		{
		NSLog(@"NSWorkspace iconForFile requires an absolute path (%@)", fullPath);
		return nil;
		}
	b=[NSBundle bundleWithPath:fullPath];
	icon=[b objectForInfoDictionaryKey:@"CFBundleIconFile"];
	if(icon && [icon length] > 0)
		{ // bundle exists and an icon has been defined
		icon=[icon stringByDeletingPathExtension];
#if 0
		NSLog(@"bundle icon file=%@", icon);
#endif
		icon=[b pathForResource:icon ofType:@"icns"];	// try to locate icon file within bundle
		if(icon)
			{
#if 0
			NSLog(@"bundle icon resource=%@", icon);
#endif
			i=[[[NSImage alloc] initByReferencingFile:icon] autorelease];
			if(i)
				return i;	// was able to loaded
			}
		}
	if([fm fileExistsAtPath:fullPath isDirectory:&is_dir])
		{
		i=[self iconForFileType:[fullPath pathExtension]];	// try on extension
#if 0
		NSLog(@"iconForFileType:%@=%@", [fullPath pathExtension], icon);
#endif
		if(i)
			return i;	// found
		if(is_dir)
			{ // some type of directory
			NSString *home=NSHomeDirectory();
			NSString *lastComponent=[fullPath lastPathComponent];
			// check if directory contains a (hidden) "Icon\r" file
			if([fullPath isEqualToString:home])
				i=[NSImage imageNamed:@"GSHome.tiff"];
			else if([lastComponent isEqualToString:@"Applications"])
				i=[NSImage imageNamed:@"GSApplications.tiff"];
			else if([lastComponent isEqualToString:@"Games"])
				i=[NSImage imageNamed:@"GSGames.tiff"];
			else if([lastComponent isEqualToString:@"Utilities"])
				i=[NSImage imageNamed:@"GSUtilities.tiff"];
			else if([lastComponent isEqualToString:@"Network"])
				i=[NSImage imageNamed:@"GSNetwork.tiff"];
			else if([fullPath isEqualToString:@"/Users"])
				i=[NSImage imageNamed:@"GSUsers.tiff"];
			else if([fullPath isEqualToString:@"/Volumes"])
				i=[NSImage imageNamed:@"GSVolumes.tiff"];
			else if([fullPath isEqualToString:[home stringByAppendingPathComponent:@"Documents"]])
				i=[NSImage imageNamed:@"GSDocuments.tiff"];
			else if([fullPath isEqualToString:[home stringByAppendingPathComponent:@"Music"]])
				i=[NSImage imageNamed:@"GSMusic.tiff"];
			else if([fullPath isEqualToString:[home stringByAppendingPathComponent:@"Pictures"]])
				i=[NSImage imageNamed:@"GSPictures.tiff"];
			else if([fullPath isEqualToString:[home stringByAppendingPathComponent:@"Library/Favorites"]])
				i=[NSImage imageNamed:@"GSFavorites.tiff"];
			else if([fullPath isEqualToString:[home stringByAppendingPathComponent:@"Library/Preferences"]])
				i=[NSImage imageNamed:@"GSPreferences.tiff"];
			else if([fullPath isEqualToString:[home stringByAppendingPathComponent:@"Library/.Trash"]])
				i=[NSImage imageNamed:@"GSTrash.tiff"];
			if(!i)
				i=[NSImage imageNamed:@"GSFolder.tiff"];	// default folder
			}
		else
			{ // some type of file - but unknown file extension
			  // might decode more file attributes here
			if([fm isExecutableFileAtPath:fullPath])
				i=[NSImage imageNamed:@"GSUnix.tiff"];	// executable file
			else
				i=[NSImage imageNamed:@"GSDocument.tiff"];	// some default document
			}
		}
	if(i)
		return i;
	return [NSImage imageNamed: @"GSUnknown.tiff"];	// file or image does not exist
}

- (NSImage *) iconForFiles:(NSArray *) pathArray
{
	switch([pathArray count])
		{
		case 0:	return nil;	// no file
		case 1: return [self iconForFile:[pathArray objectAtIndex:0]];	// first
		}
	return [NSImage imageNamed:@"GSMultiple.tiff"];
}

- (NSImage *) iconForFileType:(NSString *) fileType
{
	NSString *path;
	if(!fileType)
		return nil;
	if(!__launchServices)
		[QSLaunchServices sharedLaunchServices];
	path=[[__launchServices preferredIdentForExtension:fileType] objectForKey:@"CFBundleTypeIconPath"];	// look up icon path for known file extensions
#if 0
	NSLog(@"%@ has iconFile %@", fileType, path);
#endif
	if([path length] > 0)	// string is not nil and not empty
		{
		return [[[NSImage alloc] initByReferencingFile:path] autorelease];	// database knows about this file type (path extension)
		}
	return nil;	// unknown
}

- (void) findApplications
{ // update database
	if (!__launchServices)
		[QSLaunchServices sharedLaunchServices];
	// check if we just have updated...
	return [__launchServices findApplications];
}

- (BOOL) unmountAndEjectDeviceAtPath:(NSString *)path		// Unmount device
{
	// FIXME: unsafe for file names containing a ' -> use NSTask instead
	if(system([[NSString stringWithFormat:@"/bin/umount '%s'", [path fileSystemRepresentation]] cString]) != 0)
		return NO;	// some error
	// check if we are a memory card that can be ejected
	// -> cardctl eject %d
	[[self notificationCenter] postNotificationName:WORKSPACE(WillUnmount)
											 object:self
										   userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
											   path, @"NSDevicePath",
											   nil]];
	// send NSWorkspaceWillUnmountNotification
	return YES;
}

static NSArray *prevList;

- (void) checkForRemovableMedia
{
	NSMutableArray *thisList;
	thisList=[NSMutableArray arrayWithCapacity:10];
#if 0
	// check /etc/mtab and /Volumes to identify removable media
	// check for changes
	// -> send NSWorkspaceDidMountNotification with NSDevicePath as the userInfo
	[[self notificationCenter] postNotificationName:WORKSPACE(DidMount)
											 object:self
										   userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
											   path, @"NSDevicePath",
											   nil]];
	[[self notificationCenter] postNotificationName:WORKSPACE(DidUnmount)
											 object:self
										   userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
											   path, @"NSDevicePath",
											   nil]];
	//      or NSWorkspaceDidUnmountNotification if it disappeared
#endif
	[prevList release];
	prevList=[thisList retain];
}

- (NSArray *) mountNewRemovableMedia
{
	[self checkForRemovableMedia];
	// wait until they are mounted
	return prevList;
}

- (NSArray *) mountedRemovableMedia
{
	return prevList;
}

- (BOOL) fileSystemChanged						{ BOOL r=__fileSystemChanged; __fileSystemChanged=NO; return r; }
- (void) noteFileSystemChanged					{ __fileSystemChanged=YES; return; }
- (void) noteFileSystemChanged:(NSString *) path { __fileSystemChanged=YES; return; }
- (void) noteUserDefaultsChanged				{ __userDefaultsChanged = YES;}

- (BOOL) userDefaultsChanged
{
	BOOL hasChanged = __userDefaultsChanged;
	__userDefaultsChanged = NO;
	return hasChanged;
}

- (NSDictionary *) activeApplication;
{
	NSLog(@"NSWorkspace get activeApplication");
	NS_DURING
		NS_VALUERETURN([[NSWorkspace _distributedWorkspace] activeApplication], NSDictionary *);
    NS_HANDLER
		NSLog(@"could not get activeApplication due to %@", [localException reason]);
	NS_ENDHANDLER
	return [NSDictionary dictionary];
}

- (NSArray *) launchedApplications;
{
	NSLog(@"NSWorkspace get launchedApplications");
    NS_DURING
		NS_VALUERETURN([[NSWorkspace _distributedWorkspace] launchedApplications], NSArray *);
    NS_HANDLER
		NSLog(@"could not get launchedApplications due to %@", [localException reason]);
	NS_ENDHANDLER
	return [NSArray array];
}

- (void) hideOtherApplications;
{
	NSLog(@"hideOtherApplications");
	NS_DURING
		[[NSWorkspace _distributedWorkspace] hideOtherApplications:getpid()];
	NS_HANDLER
		NSLog(@"could not send hideOtherApplications: message due to %@", [localException reason]);
	NS_ENDHANDLER
}

#ifdef OLD

- (void) killApplication:(NSString *) appname;
{ // kill named application, i.e. signal(SIGINT or SIGKILL, pid)
	NSLog(@"syscall: %@", [NSString stringWithFormat:@"killall -SIGINT %@", appname]);
}

- (void) activateApplication:(NSString *) appname deactivateActiveApplication:(BOOL) flag;
{ // activate named application, i.e. send Activate Apple Event
	id a=nil;
	NSLog(@"activate %@", appname);
	NS_DURING									// Try to contact a running app
		a = [NSConnection rootProxyForConnectionWithRegisteredName:appname host:@""];
	NS_HANDLER
		a = nil;										// Fatal error in DO
	NS_ENDHANDLER
	NS_DURING
		{
			[a retain];
			[a activate];
		}
		NS_HANDLER
			{
				NSRunAlertPanel(nil, [NSString stringWithFormat:
					@"Failed to contact '%@'", appname],
								@"Continue", nil, nil);
				return;
			}
			NS_ENDHANDLER
			if(flag)
				[NSApp deactivate];	// and deactivate myself
}

- (void) deactivateApplication:(NSString *) appname;
{ // deactivate named application, i.e. send Deactivate Apple Event
	id a=nil;
	NSLog(@"deactivate %@", appname);
	NS_DURING									// Try to contact a running app
		a = [NSConnection rootProxyForConnectionWithRegisteredName:appname host:@""];
	NS_HANDLER
		a = nil;										// Fatal error in DO
	NS_ENDHANDLER
	NS_DURING
		{
			[a retain];
			[a deactivate];
		}
		NS_HANDLER
			{
				NSRunAlertPanel(nil, [NSString stringWithFormat:
					@"Failed to contact '%@'", appname],
								@"Continue", nil, nil);
				return;
			}
			NS_ENDHANDLER
}

- (BOOL) checkIfApplicationResponds:(NSString *) appname;
{ // try to send Apple Event and look for response - used to detect "hanging" applications
	id a=nil;
	NSLog(@"checkIfApplicationResponds %@", appname);
	NS_DURING									// Try to contact a running app
		a = [NSConnection rootProxyForConnectionWithRegisteredName:appname host:@""];
	NS_HANDLER
		a = nil;										// Fatal error in DO
	NS_ENDHANDLER
	NSLog(@"a=%@", a);
	if(!a)
		return NO;	// could not connect
	NS_DURING
		{
			[a retain];
			[a echo];	// contact and wait for (positive) response
		}
		NS_HANDLER
			{
				return NO;
			}
			NS_ENDHANDLER
			return YES;
}

#endif

+ (id <_NSUIServerProtocol>) _distributedWorkspace;			// distributed workspace
{
	NSString *server=@"com.quantum-step.mySTEP.mySystemUIServer";
	static id _distributedWorkspace;	// distributed workspace server used for launchedApplications etc.
	if(!_distributedWorkspace)
		{
		NSLog(@"get _distributedWorkspace");
		NS_DURING
			_distributedWorkspace = [NSConnection rootProxyForConnectionWithRegisteredName:server host:nil];
#if 0
			NSLog(@"created _distributedWorkspace=%@", _distributedWorkspace);
#endif
			[_distributedWorkspace retain];
#if 0
			NSLog(@"retained");
#endif
			[((NSDistantObject *) _distributedWorkspace) setProtocolForProxy:@protocol(_NSUIServerProtocol)];
		NS_HANDLER
			NSLog(@"could not contact %@ due to %@ - %@", server, [localException name], [localException reason]);
			_distributedWorkspace=nil;	// no connection established
			// we could alternatively setup ourselves as a (local) server
		NS_ENDHANDLER
		}
#if 1
	NSLog(@"_distributedWorkspace=%@", _distributedWorkspace);
#endif
	return _distributedWorkspace;
}

+ (id <_NSInputServicesProtocol>) _inputServices;			// loginwindow bundles
{
	NSString *server=@"com.quantum-step.mySTEP.myInputServices";
	static id _inputServices;	// distributed workspace server used for launchedApplications etc.
	if(!_inputServices)
		{
#if 1
		NSLog(@"get _inputServices");
#endif
		NS_DURING
			_inputServices = [NSConnection rootProxyForConnectionWithRegisteredName:server host:nil];
#if 0
			NSLog(@"created _inputServices=%@", _inputServices);
#endif
			[_inputServices retain];
#if 0
			NSLog(@"retained %u", [_inputServices count]);
#endif
			[((NSDistantObject *) _inputServices) setProtocolForProxy:@protocol(_NSInputServicesProtocol)];
		NS_HANDLER
			NSLog(@"could not contact %@ due to %@ - %@", server, [localException name], [localException reason]);
			_inputServices=nil;	// no connection established
										// we could alternatively setup ourselves as a (local) server
		NS_ENDHANDLER
		}
#if 1
	NSLog(@"_inputServices=%@", _inputServices);
#endif
	return _inputServices;
}

// should be replaced by [[QSLaunchServices sharedLaunchServices] applicationList] etc.

- (NSDictionary *) _applicationList; { return [__launchServices applicationList]; }
- (NSDictionary *) _fileTypeList; { return [__launchServices fileTypeList]; }
+ (NSArray *) _knownApplications; { return [__launchServices knownApplications]; }

+ (NSDictionary *) _standardAboutOptions;
{
	NSMutableDictionary *d=[NSMutableDictionary dictionaryWithCapacity:6];
	id o;
	[d setObject:[[[NSAttributedString alloc] initWithString:@""] autorelease]	// should look for ressource file Credits.rtf and load as NSAttributedString
		  forKey:@"Credits"];
	if((o=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]))
		[d setObject:o forKey:@"ApplicationName"];
	else
		[d setObject:[[NSProcessInfo processInfo] processName] forKey:@"ApplicationName"];
	if((o=[NSImage imageNamed:@"NSApplicationIcon"]))
		[d setObject:o forKey:@"ApplicationIcon"];
	else if((o=[NSImage imageNamed:@"generic Application Icon"]))
		[d setObject:o forKey:@"ApplicationIcon"];
	if((o=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBuildVersion"]))
		[d setObject:o forKey:@"Version"];
	else if((o=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSHumanReadableCopyright"]))
		[d setObject:o forKey:@"Version"];
#if 0
	applicationVersion=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
	applicationVersion=[NSString stringWithFormat:@"Version %@", version];
#endif
	return d;
}

@end
