1/** <title>NSWorkspace</title>
2
3   <abstract>Workspace class</abstract>
4
5   Copyright (C) 1996-2016 Free Software Foundation, Inc.
6
7   Author: Scott Christley <scottc@net-community.com>
8   Date: 1996
9   Implementation by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
10   Date: 1998
11   Implementation by: Fred Kiefer <FredKiefer@gmx.de>
12   Date: 2001
13
14   This file is part of the GNUstep GUI Library.
15
16   This library is free software; you can redistribute it and/or
17   modify it under the terms of the GNU Lesser General Public
18   License as published by the Free Software Foundation; either
19   version 2 of the License, or (at your option) any later version.
20
21   This library is distributed in the hope that it will be useful,
22   but WITHOUT ANY WARRANTY; without even the implied warranty of
23   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
24   Lesser General Public License for more details.
25
26   You should have received a copy of the GNU Lesser General Public
27   License along with this library; see the file COPYING.LIB.
28   If not, see <http://www.gnu.org/licenses/> or write to the
29   Free Software Foundation, 51 Franklin Street, Fifth Floor,
30   Boston, MA 02110-1301, USA.
31*/
32
33#import "config.h"
34
35#include <unistd.h>
36#include <sys/types.h>
37
38#if defined(HAVE_GETMNTINFO)
39#include <sys/param.h>
40#include <sys/mount.h>
41#endif
42
43#if defined(HAVE_GETMNTENT) && defined (MNT_MEMB)
44  #if defined(HAVE_MNTENT_H)
45  #include <mntent.h>
46  #elif defined(HAVE_SYS_MNTENT_H)
47  #include <sys/mntent.h>
48  #else
49  #undef HAVE_GETMNTENT
50  #endif
51#endif /* HAVE_GETMNTENT */
52
53#if defined (HAVE_SYS_STATVFS_H)
54#include <sys/statvfs.h>
55#endif
56#if defined (HAVE_SYS_VFS_H)
57#include <sys/vfs.h>
58  #ifdef __linux__
59  #include <linux/magic.h>
60  #endif
61#endif
62
63/* FIXME Solaris uses /etc/mnttab instead of /etc/mtab, but defines
64 * MNTTAB to that path.
65 * FIXME We won't get here on Solaris at all because it defines the
66 * mntent struct in sys/mnttab.h instead of sys/mntent.h.
67 */
68# ifdef _PATH_MOUNTED
69#  define MOUNTED_PATH _PATH_MOUNTED
70# elif defined(MOUNTED)
71#  define MOUNTED_PATH MOUNTED
72# else
73#  define MNTTAB "/etc/mtab"
74#  warning "Mounted path file for you OS guessed to /etc/mtab";
75# endif
76
77#import <Foundation/NSBundle.h>
78#import <Foundation/NSData.h>
79#import <Foundation/NSDictionary.h>
80#import <Foundation/NSHost.h>
81#import <Foundation/NSLock.h>
82#import <Foundation/NSDistributedLock.h>
83#import <Foundation/NSPathUtilities.h>
84#import <Foundation/NSUserDefaults.h>
85#import <Foundation/NSTask.h>
86#import <GNUstepBase/NSTask+GNUstepBase.h>
87#import <Foundation/NSException.h>
88#import <Foundation/NSFileManager.h>
89#import <Foundation/NSNotificationQueue.h>
90#import <Foundation/NSDistributedNotificationCenter.h>
91#import <Foundation/NSConnection.h>
92#import <Foundation/NSDebug.h>
93#import <Foundation/NSProcessInfo.h>
94#import <Foundation/NSThread.h>
95#import <Foundation/NSURL.h>
96#import <Foundation/NSValue.h>
97#import "AppKit/NSWorkspace.h"
98#import "AppKit/NSApplication.h"
99#import "AppKit/NSImage.h"
100#import "AppKit/NSPasteboard.h"
101#import "AppKit/NSView.h"
102#import "AppKit/NSPanel.h"
103#import "AppKit/NSWindow.h"
104#import "AppKit/NSScreen.h"
105#import "GNUstepGUI/GSServicesManager.h"
106#import "GNUstepGUI/GSDisplayServer.h"
107#import "GSGuiPrivate.h"
108
109/* Informal protocol for method to ask an app to open a URL.
110 */
111@interface NSObject (OpenURL)
112- (BOOL) application: (NSApplication*)a openURL: (NSURL*)u;
113@end
114
115/* Private method to check that a process exists.
116 */
117@interface NSProcessInfo (Private)
118+ (BOOL)_exists: (int)pid;
119@end
120
121#define PosixExecutePermission	(0111)
122
123static NSMutableDictionary *folderPathIconDict = nil;
124static NSMutableDictionary *folderIconCache = nil;
125
126static NSImage	*folderImage = nil;
127static NSImage	*multipleFiles = nil;
128static NSImage	*unknownApplication = nil;
129static NSImage	*unknownTool = nil;
130
131static NSLock   *mlock = nil;
132
133static NSString	*GSWorkspaceNotification = @"GSWorkspaceNotification";
134static NSString *GSWorkspacePreferencesChanged =
135    @"GSWorkspacePreferencesChanged";
136
137/*
138 * Depending on the 'active' flag this returns either the currently
139 * active application or an array containing all launched apps.<br />
140 * The 'notification' argument is either nil (simply query on disk
141 * database) or a notification containing information to be used to
142 * update the database.
143 */
144static id GSLaunched(NSNotification *notification, BOOL active)
145{
146  static NSString		*path = nil;
147  static NSDistributedLock	*lock = nil;
148  NSDictionary			*info = [notification userInfo];
149  NSString			*mode = [notification name];
150  NSMutableDictionary		*file = nil;
151  NSString			*name;
152  NSDictionary			*apps = nil;
153  BOOL				modified = NO;
154  unsigned	                sleeps = 0;
155
156  [mlock lock]; // start critical section
157  if (path == nil)
158    {
159      path = [NSTemporaryDirectory()
160	stringByAppendingPathComponent: @"GSLaunchedApplications"];
161      RETAIN(path);
162      lock = [[NSDistributedLock alloc] initWithPath:
163	[path stringByAppendingPathExtension: @"lock"]];
164    }
165  if ([lock tryLock] == NO)
166    {
167      /*
168       * If the lock is really old ... assume the app has died and break it.
169       */
170      if ([[lock lockDate] timeIntervalSinceNow] < -20.0)
171        {
172	  NS_DURING
173	    {
174	      [lock breakLock];
175	    }
176	  NS_HANDLER
177	    {
178              NSLog(@"Unable to break lock %@ ... %@", lock, localException);
179	    }
180	  NS_ENDHANDLER
181        }
182      /*
183       * Retry locking several times if necessary before giving up.
184       */
185      for (sleeps = 0; sleeps < 10; sleeps++)
186	{
187	  if ([lock tryLock] == YES)
188	    {
189	      break;
190	    }
191	  sleeps++;
192	  [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
193	}
194      if (sleeps >= 10)
195        {
196          [mlock unlock];
197          NSLog(@"Unable to obtain lock %@", lock);
198          return nil;
199	}
200    }
201
202  if ([[NSFileManager defaultManager] isReadableFileAtPath: path] == YES)
203    {
204      file = [NSMutableDictionary dictionaryWithContentsOfFile: path];
205    }
206  if (file == nil)
207    {
208      file = [NSMutableDictionary dictionaryWithCapacity: 2];
209    }
210  apps = [file objectForKey: @"GSLaunched"];
211  if (apps == nil)
212    {
213      apps = [NSDictionary new];
214      [file setObject: apps forKey: @"GSLaunched"];
215      RELEASE(apps);
216    }
217
218  if (info != nil
219    && (name = [info objectForKey: @"NSApplicationName"]) != nil)
220    {
221      NSDictionary	*oldInfo = [apps objectForKey: name];
222
223      if ([mode isEqualToString:
224	NSApplicationDidResignActiveNotification] == YES
225	|| [mode isEqualToString:
226	  NSWorkspaceDidTerminateApplicationNotification] == YES)
227	{
228	  if ([name isEqual: [file objectForKey: @"GSActive"]] == YES)
229	    {
230	      [file removeObjectForKey: @"GSActive"];
231	      modified = YES;
232	    }
233	}
234      else if ([mode isEqualToString:
235	NSApplicationDidBecomeActiveNotification] == YES)
236	{
237	  if ([name isEqual: [file objectForKey: @"GSActive"]] == NO)
238	    {
239	      [file setObject: name forKey: @"GSActive"];
240	      modified = YES;
241	    }
242	}
243
244      if ([mode isEqualToString:
245	NSWorkspaceDidTerminateApplicationNotification] == YES)
246	{
247	  if (oldInfo != nil)
248	    {
249	      NSMutableDictionary	*m = [apps mutableCopy];
250
251	      [m removeObjectForKey: name];
252	      [file setObject: m forKey: @"GSLaunched"];
253	      apps = m;
254	      RELEASE(m);
255	      modified = YES;
256	    }
257	}
258      else if ([mode isEqualToString:
259	NSApplicationDidResignActiveNotification] == NO)
260	{
261	  if ([info isEqual: oldInfo] == NO)
262	    {
263	      NSMutableDictionary	*m = [apps mutableCopy];
264
265	      [m setObject: info forKey: name];
266	      [file setObject: m forKey: @"GSLaunched"];
267	      apps = m;
268	      RELEASE(m);
269	      modified = YES;
270	    }
271	}
272    }
273
274  if (modified == YES)
275    {
276      [file writeToFile: path atomically: YES];
277    }
278
279  NS_DURING
280    {
281      sleeps = 0;
282      [lock unlock];
283    }
284  NS_HANDLER
285    {
286      for (sleeps = 0; sleeps < 10; sleeps++)
287	{
288	  NS_DURING
289	    {
290	      [lock unlock];
291	      NSLog(@"Unlocked %@", lock);
292	      break;
293	    }
294	  NS_HANDLER
295	    {
296	      sleeps++;
297	      if (sleeps >= 10)
298		{
299		  NSLog(@"Unable to unlock %@", lock);
300		  break;
301		}
302	      [NSThread sleepForTimeInterval: 0.1];
303	      continue;
304	    }
305	  NS_ENDHANDLER;
306	}
307    }
308  NS_ENDHANDLER;
309  [mlock unlock];  // end critical section
310
311  if (active == YES)
312    {
313      NSString	*activeName = [file objectForKey: @"GSActive"];
314
315      if (activeName == nil)
316	{
317	  return nil;
318	}
319      return [apps objectForKey: activeName];
320    }
321  else
322    {
323      return [[file objectForKey: @"GSLaunched"] allValues];
324    }
325}
326
327@interface	_GSWorkspaceCenter: NSNotificationCenter
328{
329  NSNotificationCenter	*remote;
330}
331- (void) _handleRemoteNotification: (NSNotification*)aNotification;
332- (void) _postLocal: (NSString*)name userInfo: (NSDictionary*)info;
333@end
334
335@implementation	_GSWorkspaceCenter
336
337- (void) dealloc
338{
339  [remote removeObserver: self name: nil object: GSWorkspaceNotification];
340  RELEASE(remote);
341  [super dealloc];
342}
343
344- (id) init
345{
346  self = [super init];
347  if (self != nil)
348    {
349      remote = RETAIN([NSDistributedNotificationCenter defaultCenter]);
350      NS_DURING
351	{
352	  [remote addObserver: self
353		     selector: @selector(_handleRemoteNotification:)
354			 name: nil
355		       object: GSWorkspaceNotification];
356	}
357      NS_HANDLER
358	{
359	  NSUserDefaults	*defs = [NSUserDefaults standardUserDefaults];
360
361	  if ([defs boolForKey: @"GSLogWorkspaceTimeout"])
362	    {
363	      NSLog(@"NSWorkspace caught exception %@: %@",
364	        [localException name], [localException reason]);
365	    }
366	  else
367	    {
368	      [localException raise];
369	    }
370	}
371      NS_ENDHANDLER
372    }
373  return self;
374}
375
376/*
377 * Post notification remotely - since we are listening for distributed
378 * notifications, we will observe the notification arriving from the
379 * distributed notification center, and it will get sent out locally too.
380 */
381- (void) postNotification: (NSNotification*)aNotification
382{
383  NSNotification	*rem;
384  NSString		*name = [aNotification name];
385  NSDictionary		*info = [aNotification userInfo];
386
387  if ([name isEqual: NSWorkspaceDidTerminateApplicationNotification] == YES
388    || [name isEqual: NSWorkspaceDidLaunchApplicationNotification] == YES
389    || [name isEqualToString: NSApplicationDidBecomeActiveNotification] == YES
390    || [name isEqualToString: NSApplicationDidResignActiveNotification] == YES)
391    {
392      GSLaunched(aNotification, YES);
393    }
394
395  rem = [NSNotification notificationWithName: name
396				      object: GSWorkspaceNotification
397				    userInfo: info];
398  NS_DURING
399    {
400      [remote postNotification: rem];
401    }
402  NS_HANDLER
403    {
404      NSUserDefaults	*defs = [NSUserDefaults standardUserDefaults];
405
406      if ([defs boolForKey: @"GSLogWorkspaceTimeout"])
407	{
408	  NSLog(@"NSWorkspace caught exception %@: %@",
409	    [localException name], [localException reason]);
410	}
411      else
412	{
413	  [localException raise];
414	}
415    }
416  NS_ENDHANDLER
417}
418
419- (void) postNotificationName: (NSString*)name
420		       object: (id)object
421{
422  [self postNotification: [NSNotification notificationWithName: name
423							object: object]];
424}
425
426- (void) postNotificationName: (NSString*)name
427		       object: (id)object
428		     userInfo: (NSDictionary*)info
429{
430  [self postNotification: [NSNotification notificationWithName: name
431							object: object
432						      userInfo: info]];
433}
434
435/*
436 * Forward a notification from a remote application to observers in this
437 * application.
438 */
439- (void) _handleRemoteNotification: (NSNotification*)aNotification
440{
441  [self _postLocal: [aNotification name]
442	  userInfo: [aNotification userInfo]];
443}
444
445/*
446 * Method allowing a notification to be posted locally.
447 */
448- (void) _postLocal: (NSString*)name userInfo: (NSDictionary*)info
449{
450  NSNotification	*aNotification;
451
452  aNotification = [NSNotification notificationWithName: name
453						object: self
454					      userInfo: info];
455  [super postNotification: aNotification];
456}
457@end
458
459
460@interface NSWorkspace (Private)
461
462// Icon handling
463- (NSImage*) _extIconForApp: (NSString*)appName info: (NSDictionary*)extInfo;
464- (NSImage*) unknownFiletypeImage;
465- (NSImage*) _saveImageFor: (NSString*)iconPath;
466- (NSString*) thumbnailForFile: (NSString *)file;
467- (NSImage*) _iconForExtension: (NSString*)ext;
468- (BOOL) _extension: (NSString*)ext
469               role: (NSString*)role
470	        app: (NSString**)app;
471- (BOOL) _scheme: (NSString*)scheme
472	    role: (NSString*)role
473	     app: (NSString**)app;
474- (void) _workspacePreferencesChanged: (NSNotification *)aNotification;
475
476// application communication
477- (BOOL) _launchApplication: (NSString*)appName
478		  arguments: (NSArray*)args;
479- (id) _connectApplication: (NSString*)appName;
480- (id) _workspaceApplication;
481
482@end
483
484
485/**
486 * <p>The NSWorkspace class gathers together a large number of capabilities
487 * needed for workspace management.
488 * </p>
489 * <p>The make_services tool examines all applications (anything with a
490 * .app, .debug, or .profile suffix) in the system, local, and user Apps
491 * directories, and caches information about the services each app
492 * provides (extracted from the Info-gnustep.plist file in each application).
493 * </p>
494 * <p>In addition to the cache of services information, it builds a cache of
495 * information about all known applications (including information about file
496 * types they handle).
497 * </p>
498 * <p>NSWorkspace reads the cache and uses it to determine which application
499 * to use to open a document and which icon to use to represent that document.
500 * </p>
501 * <p>The NSWorkspace API has been extended to provide methods for
502 * finding/setting the preferred icon/application for a particular file
503 * type. NSWorkspace will use the 'best' icon/application available.
504 * </p>
505 * <p>To determine the executable to launch, if there was an
506 * Info-gnustep.plist/Info.plist in the app wrapper and it had an
507 * NSExecutable field - it uses that name. Otherwise, it tries to use
508 * the name of the app - eg. foo.app/foo <br />
509 * The executable is launched by NSTask, which handles the addition
510 * of machine/os/library path components as necessary.
511 * </p>
512 * <p>To determine the icon for a file, it uses the value from the
513 * cache of icons for the file extension, or use an 'unknown' icon.
514 * </p>
515 * <p>To determine the icon for a folder, if the folder has a '.app',
516 * '.debug' or '.profile' extension - the Info-gnustep.plist file
517 * is examined for an 'NSIcon' value and NSWorkspace tries to use that.
518 * If there is no value specified - it tries 'foo.app/foo.png'
519 * or 'foo.app/foo.tiff' or 'foo.app/.dir.png' or 'foo.app/.dir.tiff'
520 * </p>
521 * <p>If the folder was not an application wrapper, it just tries
522 * the .dir.png and .dir.tiff file.
523 * </p>
524 * <p>If no icon was available, it uses a default folder icon or a
525 * special icon for the root directory.
526 * </p>
527 * <p>The information about what file types an app can handle needs
528 * to be stored in Info-gnustep.plist in an array keyed on the name
529 * <em>NSTypes</em>, within which each value is a dictionary.<br />
530 * </p>
531 * <p>In the NSTypes fields, NSWorkspace uses NSIcon (the icon to use
532 * for the type) NSUnixExtensions (a list of file extensions
533 * corresponding to the type) and NSRole (what the app can do with
534 * documents of this type <em>Editor</em>, <em>Viewer</em>,
535 * or <em>None</em>). In the AppList cache, make_services
536 * generates a dictionary, keyed by file extension, whose values are
537 * the dictionaries containing the NSTypes dictionaries of each
538 * of the apps that handle the extension.  The NSWorkspace class
539 * makes use of this cache at runtime.
540 * </p>
541 * <p>If the Info-gnustep.plist of an application says that it
542 * can open files with a particular extension, then when NSWorkspace
543 * is asked to open such a file it will attempt to send an
544 * -application:openFile: message to the application (which must be
545 * handled by the applications delegate).  If the application is not
546 * running, NSWorkspace will instead attempt to launch the application
547 * passing the filename to open after a '-GSFilePath' flag
548 * in the command line arguments.  For a GNUstep application, the
549 * application will recognize this and invoke the -application:openFile:
550 * method passing it the file name.
551 * </p>
552 * <p>This command line argument mechanism provides a way for non-gnustep
553 * applications to be used to open files simply by provideing a wrapper
554 * for them containing the appropriate Info-gnustep.plist.<br />
555 * For instance - you could set up xv.app to contain a shellscript 'xv'
556 * that would start the real xv binary passing it a file to open if the
557 * '-GSFilePath' argument was given. The Info-gnustep.plist file could look
558 * like this:
559 * </p>
560 * <example>
561 *
562 * {
563 *   NSExecutable = "xv";
564 *   NSIcon = "xv.png";
565 *   NSTypes = (
566 *     {
567 *       NSIcon = "tiff.tiff";
568 *       NSUnixExtensions = (tiff, tif);
569 *     },
570 *     {
571 *       NSIcon = "xbm.tiff";
572 *       NSUnixExtensions = (xbm);
573 *     }
574 *);
575 * }
576 * </example>
577 */
578@implementation	NSWorkspace
579
580static NSWorkspace		*sharedWorkspace = nil;
581
582static NSString			*appListPath = nil;
583static NSDictionary		*applications = nil;
584
585static NSString			*extPrefPath = nil;
586static NSDictionary		*extPreferences = nil;
587
588static NSString			*urlPrefPath = nil;
589static NSDictionary		*urlPreferences = nil;
590
591/*
592 * Class methods
593 */
594+ (void) initialize
595{
596  if (self == [NSWorkspace class])
597    {
598      static BOOL	beenHere = NO;
599      NSFileManager	*mgr = [NSFileManager defaultManager];
600      NSString		*service;
601      NSData		*data;
602      NSDictionary	*dict;
603
604      [self setVersion: 1];
605
606      [gnustep_global_lock lock];
607      if (beenHere == YES)
608	{
609	  [gnustep_global_lock unlock];
610	  return;
611	}
612
613      beenHere = YES;
614      mlock = [NSLock new];
615
616      NS_DURING
617	{
618	  service = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
619	    NSUserDomainMask, YES) objectAtIndex: 0]
620	    stringByAppendingPathComponent: @"Services"];
621
622	  /*
623	   *	Load file extension preferences.
624	   */
625	  extPrefPath = [service
626	    stringByAppendingPathComponent: @".GNUstepExtPrefs"];
627	  RETAIN(extPrefPath);
628	  if ([mgr isReadableFileAtPath: extPrefPath] == YES)
629	    {
630	      data = [NSData dataWithContentsOfFile: extPrefPath];
631	      if (data)
632		{
633		  dict = [NSDeserializer deserializePropertyListFromData: data
634					 mutableContainers: NO];
635		  extPreferences = RETAIN(dict);
636		}
637	    }
638
639	  /*
640	   *	Load URL scheme preferences.
641	   */
642	  urlPrefPath = [service
643	    stringByAppendingPathComponent: @".GNUstepURLPrefs"];
644	  RETAIN(urlPrefPath);
645	  if ([mgr isReadableFileAtPath: urlPrefPath] == YES)
646	    {
647	      data = [NSData dataWithContentsOfFile: urlPrefPath];
648	      if (data)
649		{
650		  dict = [NSDeserializer deserializePropertyListFromData: data
651					 mutableContainers: NO];
652		  urlPreferences = RETAIN(dict);
653		}
654	    }
655
656	  /*
657	   *	Load cached application information.
658	   */
659	  appListPath = [service
660	    stringByAppendingPathComponent: @".GNUstepAppList"];
661	  RETAIN(appListPath);
662	  if ([mgr isReadableFileAtPath: appListPath] == YES)
663	    {
664	      data = [NSData dataWithContentsOfFile: appListPath];
665	      if (data)
666		{
667		  dict = [NSDeserializer deserializePropertyListFromData: data
668					 mutableContainers: NO];
669		  applications = RETAIN(dict);
670		}
671	    }
672	}
673      NS_HANDLER
674	{
675	  [gnustep_global_lock unlock];
676	  [localException raise];
677	}
678      NS_ENDHANDLER
679
680      [gnustep_global_lock unlock];
681    }
682}
683
684+ (id) allocWithZone: (NSZone*)zone
685{
686  [NSException raise: NSInvalidArgumentException
687	      format: @"You may not allocate a workspace directly"];
688  return nil;
689}
690
691/*
692 * Creating a Workspace
693 */
694+ (NSWorkspace*) sharedWorkspace
695{
696  if (sharedWorkspace == nil)
697    {
698      [gnustep_global_lock lock];
699      if (sharedWorkspace == nil)
700	{
701	  sharedWorkspace =
702		(NSWorkspace*)NSAllocateObject(self, 0, NSDefaultMallocZone());
703	  [sharedWorkspace init];
704	}
705      [gnustep_global_lock unlock];
706    }
707  return sharedWorkspace;
708}
709
710/*
711 * Instance methods
712 */
713- (void) dealloc
714{
715  [NSException raise: NSInvalidArgumentException
716	      format: @"Attempt to call dealloc for shared worksapace"];
717  GSNOSUPERDEALLOC;
718}
719
720- (id) init
721{
722  NSArray *documentDir;
723  NSArray *libraryDirs;
724  NSArray *sysAppDir;
725  NSArray *appDirs;
726  NSArray *downloadDir;
727  NSArray *desktopDir;
728  NSArray *imgDir;
729  NSArray *musicDir;
730  NSArray *videoDir;
731  NSString *sysDir;
732  NSUInteger i;
733
734  if (sharedWorkspace != self)
735    {
736      RELEASE(self);
737      return RETAIN(sharedWorkspace);
738    }
739
740  [[NSNotificationCenter defaultCenter]
741    addObserver: self
742    selector: @selector(noteUserDefaultsChanged)
743    name: NSUserDefaultsDidChangeNotification
744    object: nil];
745
746  /* There's currently no way of knowing if things have changed due to
747   * apps being installed etc ... so we actually poll regularly.
748   */
749  [[NSNotificationCenter defaultCenter]
750    addObserver: self
751    selector: @selector(_workspacePreferencesChanged:)
752    name: @"GSHousekeeping"
753    object: nil];
754
755  _workspaceCenter = [_GSWorkspaceCenter new];
756  _iconMap = [NSMutableDictionary new];
757  _launched = [NSMutableDictionary new];
758  if (applications == nil)
759    {
760      [self findApplications];
761    }
762  [_workspaceCenter
763    addObserver: self
764    selector: @selector(_workspacePreferencesChanged:)
765    name: GSWorkspacePreferencesChanged
766    object: nil];
767
768  /* icon association and caching */
769  folderPathIconDict = [[NSMutableDictionary alloc] initWithCapacity:5];
770
771  documentDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
772    NSUserDomainMask, YES);
773  downloadDir = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory,
774    NSUserDomainMask, YES);
775  desktopDir = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory,
776    NSUserDomainMask, YES);
777  libraryDirs = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
778    NSAllDomainsMask, YES);
779  sysAppDir = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory,
780    NSSystemDomainMask, YES);
781  appDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory,
782    NSAllDomainsMask, YES);
783  imgDir = NSSearchPathForDirectoriesInDomains(NSPicturesDirectory,
784    NSUserDomainMask, YES);
785  musicDir = NSSearchPathForDirectoriesInDomains(NSMusicDirectory,
786    NSUserDomainMask, YES);
787  videoDir = NSSearchPathForDirectoriesInDomains(NSMoviesDirectory,
788    NSUserDomainMask, YES);
789
790  /* we try to guess a System directory and check if looks like one */
791  sysDir = nil;
792  if ([sysAppDir count] > 0)
793    {
794      sysDir = [[sysAppDir objectAtIndex: 0] stringByDeletingLastPathComponent];
795      if (![[sysDir lastPathComponent] isEqualToString: @"System"])
796	sysDir = nil;
797    }
798
799  if (sysDir != nil)
800    [folderPathIconDict setObject: @"GSFolder" forKey: sysDir];
801
802  [folderPathIconDict setObject: @"HomeDirectory"
803    forKey: NSHomeDirectory()];
804
805  /* it would be nice to use different root icons... */
806  [folderPathIconDict setObject: @"Root_PC" forKey: NSOpenStepRootDirectory()];
807
808  for (i = 0; i < [libraryDirs count]; i++)
809    {
810      [folderPathIconDict setObject: @"LibraryFolder"
811	forKey: [libraryDirs objectAtIndex: i]];
812    }
813  for (i = 0; i < [appDirs count]; i++)
814    {
815      [folderPathIconDict setObject: @"ApplicationFolder"
816	forKey: [appDirs objectAtIndex: i]];
817    }
818  for (i = 0; i < [documentDir count]; i++)
819    {
820      [folderPathIconDict setObject: @"DocsFolder"
821	forKey: [documentDir objectAtIndex: i]];
822    }
823  for (i = 0; i < [downloadDir count]; i++)
824    {
825      [folderPathIconDict setObject: @"DownloadFolder"
826	forKey: [downloadDir objectAtIndex: i]];
827    }
828  for (i = 0; i < [desktopDir count]; i++)
829    {
830      [folderPathIconDict setObject: @"Desktop"
831	forKey: [desktopDir objectAtIndex: i]];
832    }
833  for (i = 0; i < [imgDir count]; i++)
834    {
835      [folderPathIconDict setObject: @"ImageFolder"
836	forKey: [imgDir objectAtIndex: i]];
837    }
838  for (i = 0; i < [musicDir count]; i++)
839    {
840      [folderPathIconDict setObject: @"MusicFolder"
841	forKey: [musicDir objectAtIndex: i]];
842    }
843  for (i = 0; i < [videoDir count]; i++)
844    {
845      [folderPathIconDict setObject: @"VideoFolder"
846	forKey: [videoDir objectAtIndex: i]];
847    }
848  folderIconCache = [[NSMutableDictionary alloc] init];
849
850  return self;
851}
852
853/*
854 * Opening Files
855 */
856- (BOOL) _openUnknown: (NSString*)fullPath
857{
858  NSString *tool = [[NSUserDefaults standardUserDefaults] objectForKey: @"GSUnknownFileTool"];
859  NSString *launchPath;
860
861  if ((tool == nil) || (launchPath = [NSTask launchPathForTool: tool]) == nil)
862    {
863#ifdef __MINGW32__
864      // Maybe we should rather use "Explorer.exe /e, " as the tool name
865      unichar *buffer = (unichar *)calloc(1, ([fullPath length] + 1) * sizeof(unichar));
866      [fullPath getCharacters: buffer range: NSMakeRange(0, [fullPath length])];
867      buffer[[fullPath length]] = 0;
868      BOOL success = ((int)ShellExecuteW(GetDesktopWindow(), L"open", buffer, NULL,
869                                    NULL, SW_SHOWNORMAL) > 32);
870      free(buffer);
871      return success;
872#else
873      // Fall back to xdg-open
874      launchPath = [NSTask launchPathForTool: @"xdg-open"];
875#endif
876    }
877
878  if (launchPath)
879    {
880      NSTask * task = [NSTask launchedTaskWithLaunchPath: launchPath
881                                               arguments: [NSArray arrayWithObject: fullPath]];
882      if (task != nil)
883        {
884          [task waitUntilExit];
885          if ([task terminationStatus] == 0)
886            return YES;
887        }
888    }
889
890  return NO;
891}
892
893- (BOOL) openFile: (NSString*)fullPath
894{
895  return [self openFile: fullPath withApplication: nil];
896}
897
898- (BOOL) openFile: (NSString*)fullPath
899        fromImage: (NSImage*)anImage
900	       at: (NSPoint)point
901	   inView: (NSView*)aView
902{
903  NSWindow *win = [aView window];
904  NSPoint screenLoc = [win convertBaseToScreen:
905			[aView convertPoint: point toView: nil]];
906  NSSize screenSize = [[win screen] frame].size;
907  NSPoint screenCenter = NSMakePoint(screenSize.width / 2,
908				     screenSize.height / 2);
909
910  [self slideImage: anImage from: screenLoc to: screenCenter];
911  return [self openFile: fullPath];
912}
913
914- (BOOL) openFile: (NSString*)fullPath
915  withApplication: (NSString*)appName
916{
917  return [self openFile: fullPath withApplication: appName andDeactivate: YES];
918}
919
920- (BOOL) openFile: (NSString*)fullPath
921  withApplication: (NSString*)appName
922    andDeactivate: (BOOL)flag
923{
924  id app;
925
926  NS_DURING
927    {
928      if ((app = [self _workspaceApplication]) != nil)
929	{
930	  BOOL	result;
931
932	  result = [app openFile: fullPath
933		 withApplication: appName
934		   andDeactivate: flag];
935	  NS_VALRETURN(result);
936	}
937    }
938  NS_HANDLER
939    // workspace manager problem ... fall through to default code
940  NS_ENDHANDLER
941
942  if (appName == nil)
943    {
944      NSString *ext = [fullPath pathExtension];
945
946      if ([self _extension: ext role: nil app: &appName] == NO)
947        {
948          if ([self _openUnknown: fullPath])
949            {
950              return YES;
951            }
952          else
953            {
954              NSWarnLog(@"No known applications for file extension '%@'", ext);
955              return NO;
956            }
957	}
958    }
959
960  app = [self _connectApplication: appName];
961  if (app == nil)
962    {
963      NSArray *args;
964
965      args = [NSArray arrayWithObjects: @"-GSFilePath", fullPath, nil];
966      return [self _launchApplication: appName arguments: args];
967    }
968  else
969    {
970      NS_DURING
971	{
972	  if (flag == NO)
973	    {
974	      [app application: NSApp openFileWithoutUI: fullPath];
975	    }
976	  else
977	    {
978	      [app application: NSApp openFile: fullPath];
979	    }
980	}
981      NS_HANDLER
982	{
983	  NSWarnLog(@"Failed to contact '%@' to open file", appName);
984	  return NO;
985	}
986      NS_ENDHANDLER
987    }
988  if (flag)
989    {
990      [NSApp deactivate];
991    }
992  return YES;
993}
994
995- (BOOL) openTempFile: (NSString*)fullPath
996{
997  id app;
998  NSString *appName;
999  NSString *ext;
1000
1001  NS_DURING
1002    {
1003      if ((app = [self _workspaceApplication]) != nil)
1004	{
1005	  BOOL	result;
1006
1007	  result = [app openTempFile: fullPath];
1008	  NS_VALRETURN(result);
1009	}
1010    }
1011  NS_HANDLER
1012    // workspace manager problem ... fall through to default code
1013  NS_ENDHANDLER
1014
1015  ext = [fullPath pathExtension];
1016  if ([self _extension: ext role: nil app: &appName] == NO)
1017    {
1018      if ([self _openUnknown: fullPath])
1019        {
1020          return YES;
1021        }
1022      else
1023        {
1024          NSWarnLog(@"No known applications for file extension '%@'", ext);
1025          return NO;
1026        }
1027    }
1028
1029  app = [self _connectApplication: appName];
1030  if (app == nil)
1031    {
1032      NSArray *args;
1033
1034      args = [NSArray arrayWithObjects: @"-GSTempPath", fullPath, nil];
1035      return [self _launchApplication: appName arguments: args];
1036    }
1037  else
1038    {
1039      NS_DURING
1040	{
1041	  [app application: NSApp openTempFile: fullPath];
1042	}
1043      NS_HANDLER
1044	{
1045	  NSWarnLog(@"Failed to contact '%@' to open temp file", appName);
1046	  return NO;
1047	}
1048      NS_ENDHANDLER
1049    }
1050
1051  [NSApp deactivate];
1052
1053  return YES;
1054}
1055
1056- (BOOL) openURL: (NSURL*)url
1057{
1058  if ([url isFileURL])
1059    {
1060      return [self openFile: [url path]];
1061    }
1062  else
1063    {
1064      NSString		*appName;
1065      NSPasteboard      *pb;
1066
1067      appName = [self getBestAppInRole: nil forScheme: [url scheme]];
1068      if (appName != nil)
1069	{
1070	  id app;
1071
1072	  /* Now try to get the application to open the URL.
1073	   */
1074	  app = GSContactApplication(appName, nil, nil);
1075	  if (app != nil)
1076	    {
1077	      NS_DURING
1078		{
1079	          [app application: NSApp openURL: url];
1080		}
1081	      NS_HANDLER
1082		{
1083		  NSWarnLog(@"Failed to contact '%@' to open file", appName);
1084		  return NO;
1085		}
1086	      NS_ENDHANDLER
1087	      [NSApp deactivate];
1088	      return YES;
1089	    }
1090	}
1091      /* No application found to open the URL.
1092       * Try any OpenURL service available.
1093       */
1094      pb = [NSPasteboard pasteboardWithUniqueName];
1095      [pb declareTypes: [NSArray arrayWithObject: NSURLPboardType]
1096                         owner: nil];
1097     [url writeToPasteboard: pb];
1098     if (NSPerformService(@"OpenURL", pb))
1099       {
1100         return YES;
1101       }
1102     else
1103       {
1104         return [self _openUnknown: [url absoluteString]];
1105       }
1106    }
1107}
1108
1109/*
1110 * Manipulating Files
1111 */
1112- (BOOL) performFileOperation: (NSString*)operation
1113		       source: (NSString*)source
1114		  destination: (NSString*)destination
1115		        files: (NSArray*)files
1116			  tag: (NSInteger*)tag
1117{
1118  id app;
1119
1120  NS_DURING
1121    {
1122      if ((app = [self _workspaceApplication]) != nil)
1123	{
1124	  BOOL	result;
1125
1126	  result = [app performFileOperation: operation
1127				      source: source
1128				 destination: destination
1129				       files: files
1130					 tag: tag];
1131	  NS_VALRETURN(result);
1132	}
1133    }
1134  NS_HANDLER
1135    // workspace manager problem ... fall through to default code
1136  NS_ENDHANDLER
1137
1138  return NO;
1139}
1140
1141- (BOOL) selectFile: (NSString*)fullPath
1142inFileViewerRootedAtPath: (NSString*)rootFullpath
1143{
1144  id app;
1145
1146  NS_DURING
1147    {
1148      if ((app = [self _workspaceApplication]) != nil)
1149	{
1150	  BOOL	result;
1151
1152	  result = [app selectFile: fullPath
1153	  inFileViewerRootedAtPath: rootFullpath];
1154	  NS_VALRETURN(result);
1155	}
1156    }
1157  NS_HANDLER
1158    // workspace manager problem ... fall through to default code
1159  NS_ENDHANDLER
1160
1161  return NO;
1162}
1163
1164/**
1165 * Given an application name, return the full path for that application.<br />
1166 * This method looks for the application in standard locations, and if not
1167 * found there, according to MacOS-X documentation, returns nil.<br />
1168 * If the supplied application name is an absolute path, returns that path
1169 * irrespective of whether such an application exists or not.  This is
1170 * <em>not</em> the docmented debavior in the MacOS-X documentation, but is
1171 * the MacOS-X implemented behavior.<br />
1172 * If the appName has an extension, it is used, otherwise in GNUstep
1173 * the standard app, debug, and profile extensions * are tried.<br />
1174 */
1175- (NSString*) fullPathForApplication: (NSString*)appName
1176{
1177  NSString	*base;
1178  NSString	*path;
1179  NSString	*ext;
1180
1181  if ([appName length] == 0)
1182    {
1183      return nil;
1184    }
1185  if ([[appName lastPathComponent] isEqual: appName] == NO)
1186    {
1187      if ([appName isAbsolutePath] == YES)
1188	{
1189	  return appName;		// MacOS-X implementation behavior.
1190	}
1191      /*
1192       * Relative path ... get standarized absolute path
1193       */
1194      path = [[NSFileManager defaultManager] currentDirectoryPath];
1195      appName = [path stringByAppendingPathComponent: appName];
1196      appName = [appName stringByStandardizingPath];
1197    }
1198  base = [appName stringByDeletingLastPathComponent];
1199  appName = [appName lastPathComponent];
1200  ext = [appName pathExtension];
1201  if ([ext length] == 0) // no extension, let's find one
1202    {
1203      path = [appName stringByAppendingPathExtension: @"app"];
1204      path = [applications objectForKey: path];
1205      if (path == nil)
1206	{
1207	  path = [appName stringByAppendingPathExtension: @"debug"];
1208	  path = [applications objectForKey: path];
1209	}
1210      if (path == nil)
1211	{
1212	  path = [appName stringByAppendingPathExtension: @"profile"];
1213	  path = [applications objectForKey: path];
1214	}
1215    }
1216  else
1217    {
1218      path = [applications objectForKey: appName];
1219    }
1220
1221  /*
1222   * If the original name included a path, check that the located name
1223   * matches it.  If it doesn't we return nil as MacOS-X does.
1224   */
1225  if ([base length] > 0
1226    && [base isEqual: [path stringByDeletingLastPathComponent]] == NO)
1227    {
1228      path = nil;
1229    }
1230  return path;
1231}
1232
1233- (BOOL) getFileSystemInfoForPath: (NSString*)fullPath
1234		      isRemovable: (BOOL*)removableFlag
1235		       isWritable: (BOOL*)writableFlag
1236		    isUnmountable: (BOOL*)unmountableFlag
1237		      description: (NSString **)description
1238			     type: (NSString **)fileSystemType
1239{
1240  NSArray *removables;
1241  NSString  *fsName;
1242
1243  /* since we might not be able to get information about removable volumes
1244     we use the information from the preferences which can be set in SystemPreferences
1245   */
1246  removables = [[[NSUserDefaults standardUserDefaults] persistentDomainForName: NSGlobalDomain] objectForKey: @"GSRemovableMediaPaths"];
1247
1248  *removableFlag = NO;
1249  if ([removables containsObject: fullPath])
1250    *removableFlag = YES;
1251
1252  fsName = nil;
1253#if defined(HAVE_GETMNTENT) && defined (MNT_MEMB)
1254  // if this is called from mountedLocalVolumePaths, the getmntent is searched again for each item
1255  FILE		*fptr = setmntent(MOUNTED_PATH, "r");
1256  struct mntent	*me;
1257
1258  while ((me = getmntent(fptr)) != 0)
1259  {
1260    if (strcmp(me->MNT_MEMB, [fullPath fileSystemRepresentation]) == 0)
1261      {
1262	fsName = [NSString stringWithCString:me->MNT_FSNAME];
1263      }
1264  }
1265  endmntent(fptr);
1266#endif /* HAVE_GETMINTENT */
1267  if (fsName && [fsName hasPrefix:@"/dev"])
1268    {
1269      NSString *devName;
1270      NSString *devInfoPath;
1271      BOOL r;
1272      NSString *removableString;
1273
1274      r = NO;
1275      devName = [fsName lastPathComponent];
1276      // This is a very crude way of removing the partition number
1277      if ([devName length] > 3)
1278	devName = [devName substringToIndex: 3];
1279
1280      devInfoPath = [@"/sys/block" stringByAppendingPathComponent:devName];
1281      devInfoPath = [devInfoPath stringByAppendingPathComponent:@"removable"];
1282
1283      removableString = [[NSString alloc] initWithContentsOfFile:devInfoPath];
1284
1285      if ([removableString hasPrefix:@"1"])
1286	r = YES;
1287      [removableString release];
1288
1289      // we go in OR against the informatoin derived from declared removables
1290      // so we enrich, but don't mark removables as not
1291      *removableFlag |= r;
1292    }
1293
1294
1295#if defined (HAVE_SYS_STATVFS_H) || defined (HAVE_SYS_VFS_H)
1296  /* We use statvfs() if available to get information but statfs()
1297     will provide more information on different systems, but in a non
1298     standard way. Thus e.g. on Linux two calls are needed.
1299     The NetBSD statvfs is a statfs in disguise, i.e., it provides all
1300     information available in      the 4.4BSD statfs call.
1301     Other BSDs and      Linuxes have statvfs as well, but this returns less
1302     information than      the 4.4BSD statfs call.
1303     Note that the POSIX statvfs is not really helpful for us here. The
1304     only information that could be extracted from the data returned by
1305     that syscall is the ST_RDONLY flag. There is no owner field nor a
1306     typename.
1307     The statvfs call on Solaris returns a structure that includes a
1308     non-standard f_basetype field, which provides the name of the
1309     underlying file system type.
1310  */
1311  BOOL isRootFS;
1312  BOOL hasOwnership;
1313
1314#if defined(HAVE_STATVFS)
1315  #define USING_STATVFS 1
1316  struct statvfs m;
1317  if (statvfs([fullPath fileSystemRepresentation], &m))
1318    return NO;
1319#elif defined (HAVE_STATFS)
1320  #define USING_STATFS 1
1321  struct statfs m;
1322  if (statfs([fullPath fileSystemRepresentation], &m))
1323    return NO;
1324#endif
1325
1326  *writableFlag = 1;
1327#if  defined(HAVE_STRUCT_STATVFS_F_FLAG)
1328  *writableFlag = (m.f_flag & ST_RDONLY) == 0;
1329#elif defined(HAVE_STRUCT_STATFS_F_FLAGS)
1330  *writableFlag = (m.f_flags & ST_RDONLY) == 0;
1331#endif
1332
1333
1334  isRootFS = NO;
1335#if defined(ST_ROOTFS)
1336  isRootFS = (m.f_flag & ST_ROOTFS);
1337#elif defined (MNT_ROOTFS)
1338  isRootFS = (m.f_flag & MNT_ROOTFS);
1339#endif
1340
1341  hasOwnership = NO;
1342#if (defined(USING_STATFS) && defined(HAVE_STRUCT_STATFS_F_OWNER)) || (defined(USING_STATVFS) &&  defined(HAVE_STRUCT_STATVFS_F_OWNER))
1343  uid_t uid = geteuid();
1344  if (uid == 0 || uid == m.f_owner)
1345    hasOwnership = YES;
1346#elif (defined(USING_STATVFS) && !defined(USING_STATFS) && defined (HAVE_STATFS) && defined(HAVE_STRUCT_STATFS_F_OWNER))
1347  uid_t uid = geteuid();
1348  // FreeBSD only?
1349  struct statfs m2;
1350  statfs([fullPath fileSystemRepresentation], &m2);
1351  if (uid == 0 || uid == m2.f_owner)
1352    hasOwnership = YES;
1353#endif
1354
1355  *unmountableFlag = !isRootFS && hasOwnership;
1356
1357  *description = @"filesystem"; // FIXME
1358
1359  *fileSystemType = nil;
1360#if defined (__linux__)
1361  struct statfs m2;
1362
1363  statfs([fullPath fileSystemRepresentation], &m2);
1364  if (m2.f_type == EXT2_SUPER_MAGIC)
1365    *fileSystemType = @"EXT2";
1366  else if (m2.f_type == EXT3_SUPER_MAGIC)
1367    *fileSystemType = @"EXT3";
1368  else if (m2.f_type == EXT4_SUPER_MAGIC)
1369    *fileSystemType = @"EXT4";
1370  else if (m2.f_type == ISOFS_SUPER_MAGIC)
1371    *fileSystemType = @"ISO9660";
1372#ifdef JFS_SUPER_MAGIC
1373  else if (m2.f_type == JFS_SUPER_MAGIC)
1374    *fileSystemType = @"JFS";
1375#endif
1376  else if (m2.f_type == MSDOS_SUPER_MAGIC)
1377    *fileSystemType = @"MSDOS";
1378  else if (m2.f_type == NFS_SUPER_MAGIC)
1379    *fileSystemType = @"NFS";
1380  else
1381    *fileSystemType = @"Other";
1382#elif defined(__sun__)
1383  *fileSystemType =
1384    [[NSString alloc] initWithCString: m.f_basetype encoding: [NSString defaultCStringEncoding]];
1385#elif !defined(__GNU__)
1386  // FIXME we disable this for HURD, but we need to check for struct member in configure
1387  //  *fileSystemType = [[NSString alloc] initWithCString: m.f_fstypename encoding: [NSString defaultCStringEncoding]];
1388#endif
1389
1390#else /* no statfs() nor statvfs() */
1391  NSLog(@"getFileSystemInfoForPath not supported on your OS");
1392#endif
1393
1394  return YES;
1395}
1396
1397/**
1398 * This method gets information about the file at fullPath and
1399 * returns YES on success, NO if the named file could not be
1400 * found.<br />
1401 * On success, the name of the preferred application for opening
1402 * the file is returned in *appName, or nil if there is no known
1403 * application to open it.<br />
1404 * The returned value in *type describes the file using one of
1405 * the following constants.
1406 * <deflist>
1407 *   <term>NSPlainFileType</term>
1408 *   <desc>
1409 *     A plain file or a directory that some application
1410 *     claims to be able to open like a file.
1411 *   </desc>
1412 *   <term>NSDirectoryFileType</term>
1413 *   <desc>An untyped directory</desc>
1414 *   <term>NSApplicationFileType</term>
1415 *   <desc>A GNUstep application</desc>
1416 *   <term>NSFilesystemFileType</term>
1417 *   <desc>A file system mount point</desc>
1418 *   <term>NSShellCommandFileType</term>
1419 *   <desc>Executable shell command</desc>
1420 * </deflist>
1421 */
1422- (BOOL) getInfoForFile: (NSString*)fullPath
1423	    application: (NSString **)appName
1424		   type: (NSString **)type
1425{
1426  NSFileManager	*fm = [NSFileManager defaultManager];
1427  NSDictionary	*attributes;
1428  NSString	*fileType;
1429  NSString	*extension = [fullPath pathExtension];
1430
1431  attributes = [fm fileAttributesAtPath: fullPath traverseLink: YES];
1432
1433  if (attributes != nil)
1434    {
1435      *appName = [self getBestAppInRole: nil forExtension: extension];
1436      fileType = [attributes fileType];
1437      if ([fileType isEqualToString: NSFileTypeRegular])
1438	{
1439	  if ([attributes filePosixPermissions] & PosixExecutePermission)
1440	    {
1441	      *type = NSShellCommandFileType;
1442	    }
1443	  else
1444	    {
1445	      *type = NSPlainFileType;
1446	    }
1447	}
1448      else if ([fileType isEqualToString: NSFileTypeDirectory])
1449	{
1450	  if ([extension isEqualToString: @"app"]
1451	    || [extension isEqualToString: @"debug"]
1452	    || [extension isEqualToString: @"profile"])
1453	    {
1454	      *type = NSApplicationFileType;
1455	    }
1456	  else if ([extension isEqualToString: @"bundle"])
1457	    {
1458	      *type = NSPlainFileType;
1459	    }
1460	  else if (*appName != nil && [extension length] > 0)
1461	    {
1462	      *type = NSPlainFileType;
1463	    }
1464	  /*
1465	   * The idea here is that if the parent directory's
1466	   * fileSystemNumber differs, this must be a filesystem
1467	   * mount point.
1468	   */
1469	  else if ([[fm fileAttributesAtPath:
1470	    [fullPath stringByDeletingLastPathComponent]
1471	    traverseLink: YES] fileSystemNumber]
1472	    != [attributes fileSystemNumber])
1473	    {
1474	      *type = NSFilesystemFileType;
1475	    }
1476	  else
1477	    {
1478	      *type = NSDirectoryFileType;
1479	    }
1480	}
1481      else
1482	{
1483	  /*
1484	   * This catches sockets, character special, block special,
1485	   * and unknown file types
1486	   */
1487	  *type = NSPlainFileType;
1488	}
1489      return YES;
1490    }
1491  else
1492    {
1493      *appName = nil;
1494      return NO;
1495    }
1496}
1497
1498- (NSImage*) iconForFile: (NSString*)fullPath
1499{
1500  NSImage	*image = nil;
1501  NSString	*pathExtension = [[fullPath pathExtension] lowercaseString];
1502  NSFileManager	*mgr = [NSFileManager defaultManager];
1503  NSDictionary	*attributes;
1504  NSString	*fileType;
1505
1506  /*
1507    If we have a symobolic link, get not only the original path attributes,
1508    but also the original path, to resolve the correct icon.
1509    mac resolves the original icon
1510  */
1511  fullPath = [fullPath stringByResolvingSymlinksInPath];
1512
1513  /* now we get the target attributes of the traversed link */
1514  attributes = [mgr fileAttributesAtPath: fullPath traverseLink: NO];
1515  fileType = [attributes fileType];
1516
1517  if ([fileType isEqual: NSFileTypeDirectory] == YES)
1518    {
1519      NSString *iconPath = nil;
1520
1521      if ([pathExtension isEqualToString: @"app"]
1522	|| [pathExtension isEqualToString: @"debug"]
1523	|| [pathExtension isEqualToString: @"profile"])
1524	{
1525	  image = [self appIconForApp: fullPath];
1526
1527	  if (image == nil)
1528	    {
1529	      /*
1530               * Just use the appropriate icon for the path extension
1531               */
1532              return [self _iconForExtension: pathExtension];
1533	    }
1534	}
1535
1536      /*
1537       * If we have no iconPath, try 'dir/.dir.png' as a
1538       * possible locations for the directory icon.
1539       */
1540      if (iconPath == nil)
1541	{
1542	  iconPath = [fullPath stringByAppendingPathComponent: @".dir.png"];
1543	  if ([mgr isReadableFileAtPath: iconPath] == NO)
1544	    {
1545	      iconPath
1546		= [fullPath stringByAppendingPathComponent: @".dir.tiff"];
1547	      if ([mgr isReadableFileAtPath: iconPath] == NO)
1548		{
1549		  iconPath = nil;
1550		}
1551	    }
1552	}
1553
1554      if (iconPath != nil)
1555	{
1556	  image = [self _saveImageFor: iconPath];
1557	}
1558
1559      if (image == nil)
1560	{
1561	  image = [self _iconForExtension: pathExtension];
1562	  if (image == nil || image == [self unknownFiletypeImage])
1563	    {
1564	      NSString *iconName;
1565
1566	      iconName = [folderPathIconDict objectForKey: fullPath];
1567	      if (iconName != nil)
1568		{
1569		  NSImage *iconImage;
1570
1571		  iconImage = [folderIconCache objectForKey: iconName];
1572		  if (iconImage == nil)
1573		    {
1574		      iconImage = [NSImage _standardImageWithName: iconName];
1575                      if (!iconImage)
1576                        {
1577                          /* no specific image found in theme, fall-back to folder */
1578                          NSLog(@"no image found for %@", iconName);
1579                          iconImage = [NSImage _standardImageWithName: @"Folder"];
1580                        }
1581                      /* the dictionary retains the image */
1582                      [folderIconCache setObject: iconImage forKey: iconName];
1583		    }
1584		  image = iconImage;
1585		}
1586	      else
1587		{
1588		  if (folderImage == nil)
1589		    {
1590		      folderImage = RETAIN([NSImage _standardImageWithName:
1591						      @"Folder"]);
1592		    }
1593		  image = folderImage;
1594		}
1595	    }
1596
1597	}
1598    }
1599  else
1600    {
1601      NSDebugLog(@"pathExtension is '%@'", pathExtension);
1602
1603      if ([[NSUserDefaults standardUserDefaults] boolForKey:
1604	      @"GSUseFreedesktopThumbnails"])
1605        {
1606	  /* This image will be 128x128 pixels as oposed to the 48x48
1607	     of other GNUstep icons or the 32x32 of the specification */
1608	  image = [self _saveImageFor: [self thumbnailForFile: fullPath]];
1609	  if (image != nil)
1610	    {
1611	      return image;
1612	    }
1613	}
1614
1615      image = [self _iconForExtension: pathExtension];
1616      if (image == nil || image == [self unknownFiletypeImage])
1617	{
1618	  NSFileManager	*mgr;
1619
1620	  mgr = [NSFileManager defaultManager];
1621	  if ([mgr isExecutableFileAtPath: fullPath] == YES)
1622	    {
1623	      NSDictionary	*attributes;
1624	      NSString		*fileType;
1625
1626	      attributes = [mgr fileAttributesAtPath: fullPath
1627					traverseLink: YES];
1628	      fileType = [attributes objectForKey: NSFileType];
1629	      if ([fileType isEqual: NSFileTypeRegular] == YES)
1630		{
1631		  if (unknownTool == nil)
1632		    {
1633		      unknownTool = RETAIN([NSImage _standardImageWithName:
1634			@"UnknownTool"]);
1635		    }
1636		  image = unknownTool;
1637		}
1638	    }
1639	}
1640    }
1641
1642  if (image == nil)
1643    {
1644      image = [self unknownFiletypeImage];
1645    }
1646
1647  return image;
1648}
1649
1650- (NSImage*) iconForFiles: (NSArray*)pathArray
1651{
1652  if ([pathArray count] == 1)
1653    {
1654      return [self iconForFile: [pathArray objectAtIndex: 0]];
1655    }
1656  if (multipleFiles == nil)
1657    {
1658      // FIXME: Icon does not exist
1659      multipleFiles = [NSImage imageNamed: @"FileIcon_multi"];
1660    }
1661
1662  return multipleFiles;
1663}
1664
1665- (NSImage*) iconForFileType: (NSString*)fileType
1666{
1667  return [self _iconForExtension: fileType];
1668}
1669
1670- (BOOL) isFilePackageAtPath: (NSString*)fullPath
1671{
1672  NSFileManager	*mgr = [NSFileManager defaultManager];
1673  NSDictionary	*attributes;
1674  NSString	*fileType, *extension;
1675
1676  attributes = [mgr fileAttributesAtPath: fullPath traverseLink: YES];
1677  fileType = [attributes objectForKey: NSFileType];
1678  if ([fileType isEqual: NSFileTypeDirectory] == YES)
1679    {
1680      /*
1681       * We return YES here exactly when getInfoForFile:application:type:
1682       * considers the directory an application or a plain file
1683       */
1684      extension = [fullPath pathExtension];
1685      if ([extension isEqualToString: @"app"]
1686	|| [extension isEqualToString: @"debug"]
1687	|| [extension isEqualToString: @"profile"]
1688        || [extension isEqualToString: @"bundle"])
1689        {
1690	  return YES;
1691	}
1692      else if ([extension length] > 0
1693        && [self getBestAppInRole: nil forExtension: extension] != nil)
1694        {
1695	  return YES;
1696        }
1697    }
1698  return NO;
1699}
1700
1701- (BOOL) setIcon: (NSImage *)image
1702         forFile: (NSString *)fullPath
1703         options: (NSWorkspaceIconCreationOptions)options
1704{
1705  // FIXME
1706  return NO;
1707}
1708
1709/**
1710 * Tracking Changes to the File System
1711 */
1712- (BOOL) fileSystemChanged
1713{
1714  BOOL flag = _fileSystemChanged;
1715
1716  _fileSystemChanged = NO;
1717  return flag;
1718}
1719
1720- (void) noteFileSystemChanged
1721{
1722  _fileSystemChanged = YES;
1723}
1724
1725- (void) noteFileSystemChanged: (NSString*)path
1726{
1727  _fileSystemChanged = YES;
1728}
1729
1730/**
1731 * Updates Registered Services, File Types, and other information about any
1732 * applications installed in the standard locations.
1733 */
1734- (void) findApplications
1735{
1736  static NSString	*path = nil;
1737  NSTask		*task;
1738
1739  /*
1740   * Try to locate and run an executable copy of 'make_services'
1741   */
1742  if (path == nil)
1743    {
1744      path = [[NSTask launchPathForTool: @"make_services"] retain];
1745    }
1746  task = [NSTask launchedTaskWithLaunchPath: path
1747				  arguments: nil];
1748  if (task != nil)
1749    {
1750      [task waitUntilExit];
1751    }
1752  [self _workspacePreferencesChanged:
1753     [NSNotification notificationWithName: GSWorkspacePreferencesChanged
1754				   object: self]];
1755}
1756
1757/**
1758 * Instructs all the other running applications to hide themselves.
1759 * <em>not yet implemented</em>
1760 */
1761- (void) hideOtherApplications
1762{
1763  // FIXME
1764}
1765
1766/**
1767 * Calls -launchApplication:showIcon:autolaunch: with arguments set to
1768 * show the icon but not set it up as an autolaunch.
1769 */
1770- (BOOL) launchApplication: (NSString*)appName
1771{
1772  return [self launchApplication: appName
1773			showIcon: YES
1774		      autolaunch: NO];
1775}
1776
1777/**
1778 * <p>Launches the specified application (unless it is already running).<br />
1779 * If the autolaunch flag is yes, sets the autolaunch user default for the
1780 * newly launched application, so that applications which understand the
1781 * concept of being autolaunched at system startup time can modify their
1782 * behavior appropriately.
1783 * </p>
1784 * <p>Sends an NSWorkspaceWillLaunchApplicationNotification before it
1785 * actually attempts to launch the application (this is not sent if the
1786 * application is already running).
1787 * </p>
1788 * <p>The application sends an NSWorkspaceDidlLaunchApplicationNotification
1789 * on completion of launching.  This is not sent if the application is already
1790 * running, or if it fails to complete its startup.
1791 * </p>
1792 * <p>Returns NO if the application cannot be launched (eg. it does not exist
1793 * or the binary is not executable).
1794 * </p>
1795 * <p>Returns YES if the application was already running or of it was launched
1796 * (this does not necessarily mean that the application succeeded in starting
1797 * up fully).
1798 * </p>
1799 * <p>Once an application has fully started up, you should be able to connect
1800 * to it using [NSConnection+rootProxyForConnectionWithRegisteredName:host:]
1801 * passing the application name (normally the filesystem name excluding path
1802 * and file extension) and an empty host name.  This will let you communicate
1803 * with the the [NSApplication-delegate] of the launched application, and you
1804 * can generally use this as a test of whether an application is running
1805 * correctly.
1806 * </p>
1807 */
1808- (BOOL) launchApplication: (NSString*)appName
1809		  showIcon: (BOOL)showIcon
1810	        autolaunch: (BOOL)autolaunch
1811{
1812  id 	app;
1813
1814  NS_DURING
1815    {
1816      if ((app = [self _workspaceApplication]) != nil)
1817	{
1818	  BOOL	result;
1819
1820	  result = [app launchApplication: appName
1821	   			 showIcon: showIcon
1822			       autolaunch: autolaunch];
1823	  NS_VALRETURN(result);
1824	}
1825    }
1826  NS_HANDLER
1827    // workspace manager problem ... fall through to default code
1828  NS_ENDHANDLER
1829
1830  app = [self _connectApplication: appName];
1831  if (app == nil)
1832    {
1833      NSArray	*args = nil;
1834
1835      if (autolaunch == YES)
1836	{
1837	  args = [NSArray arrayWithObjects: @"-autolaunch", @"YES", nil];
1838	}
1839      return [self _launchApplication: appName arguments: args];
1840    }
1841  else
1842    {
1843      [app activateIgnoringOtherApps:YES];
1844    }
1845
1846  return YES;
1847}
1848
1849- (NSString *) absolutePathForAppBundleWithIdentifier: (NSString *)bundleIdentifier
1850{
1851  // TODO: full implementation
1852  return [self fullPathForApplication: bundleIdentifier];
1853}
1854
1855- (BOOL) launchAppWithBundleIdentifier: (NSString *)bundleIdentifier
1856			       options: (NSWorkspaceLaunchOptions)options
1857	additionalEventParamDescriptor: (NSAppleEventDescriptor *)descriptor
1858		      launchIdentifier: (NSNumber **)identifier
1859{
1860  // TODO: full implementation
1861  return [self launchApplication: bundleIdentifier
1862			showIcon: YES
1863		      autolaunch: NO];
1864}
1865
1866- (BOOL) openURLs: (NSArray *)urls
1867withAppBundleIdentifier: (NSString *)bundleIdentifier
1868          options: (NSWorkspaceLaunchOptions)options
1869additionalEventParamDescriptor: (NSAppleEventDescriptor *)descriptor
1870launchIdentifiers: (NSArray **)identifiers
1871{
1872  // FIXME
1873  return NO;
1874}
1875
1876/**
1877 * Returns a description of the currently active application, containing
1878 * the name (NSApplicationName), path (NSApplicationPath) and process
1879 * identifier (NSApplicationProcessIdentifier).<br />
1880 * Returns nil if there is no known active application.
1881 */
1882- (NSDictionary*) activeApplication
1883{
1884  id	app;
1885
1886  NS_DURING
1887    {
1888      if ((app = [self _workspaceApplication]) != nil)
1889	{
1890	  NSDictionary	*result;
1891
1892	  result = [app activeApplication];
1893	  NS_VALRETURN(result);
1894	}
1895    }
1896  NS_HANDLER
1897    // workspace manager problem ... fall through to default code
1898  NS_ENDHANDLER
1899
1900  return GSLaunched(nil, YES);
1901}
1902
1903/**
1904 * Returns an array listing all the applications known to have been
1905 * launched.  Each entry in the array is a dictionary providing
1906 * the name, path and process identfier of an application.
1907 */
1908- (NSArray*) launchedApplications
1909{
1910  NSArray       *apps = nil;
1911
1912  NS_DURING
1913    {
1914      id	app;
1915
1916      if ((app = [self _workspaceApplication]) != nil)
1917	{
1918	  apps = [app launchedApplications];
1919	}
1920    }
1921  NS_HANDLER
1922    {
1923      // workspace manager problem ... fall through to default code
1924    }
1925  NS_ENDHANDLER
1926
1927  if (apps == nil)
1928    {
1929      NSMutableArray    *m;
1930      unsigned          count;
1931
1932      apps = GSLaunched(nil, NO);
1933      apps = m = AUTORELEASE([apps mutableCopy]);
1934      if ((count = [apps count]) > 0)
1935        {
1936          if ([NSProcessInfo respondsToSelector: @selector(_exists:)] == YES)
1937            {
1938              /* Check and remove apps whose pid no longer exists
1939               */
1940              while (count-- > 0)
1941                {
1942                  int       pid;
1943                  NSString  *name;
1944
1945                  name = [[apps objectAtIndex: count]
1946                    objectForKey: @"NSApplicationName"];
1947                  pid = [[[apps objectAtIndex: count]
1948                    objectForKey: @"NSApplicationProcessIdentifier"] intValue];
1949                  if (pid > 0 && [name length] > 0)
1950                    {
1951                      if ([NSProcessInfo _exists: pid] == NO)
1952                        {
1953                          GSLaunched([NSNotification notificationWithName:
1954                            NSWorkspaceDidTerminateApplicationNotification
1955                            object: self
1956                            userInfo: [NSDictionary dictionaryWithObject: name
1957                              forKey: @"NSApplicationName"]], NO);
1958                          [m removeObjectAtIndex: count];
1959                        }
1960                    }
1961                }
1962            }
1963        }
1964    }
1965  return apps;
1966}
1967
1968/*
1969 * Unmounting a Device and eject if possible
1970 */
1971- (BOOL) unmountAndEjectDeviceAtPath: (NSString*)path
1972{
1973  NSUInteger    systype = [[NSProcessInfo processInfo] operatingSystem];
1974  NSDictionary	*userinfo;
1975  NSTask	*task;
1976
1977  /* let's check if it is a local volume we may unmount */
1978  if (![[self mountedLocalVolumePaths] containsObject:path])
1979    {
1980      NSLog(@"unmountAndEjectDeviceAtPath: Path %@ not mounted", path);
1981      return NO;
1982    }
1983
1984  userinfo = [NSDictionary dictionaryWithObject: path
1985					 forKey: @"NSDevicePath"];
1986  [_workspaceCenter postNotificationName: NSWorkspaceWillUnmountNotification
1987				  object: self
1988				userInfo: userinfo];
1989  task = [NSTask launchedTaskWithLaunchPath: @"umount"
1990				  arguments: [NSArray arrayWithObject: path]];
1991
1992  if (task)
1993    {
1994      [task waitUntilExit];
1995      if ([task terminationStatus] != 0)
1996	{
1997	  return NO;
1998	}
1999    }
2000  else
2001    {
2002      return NO;
2003    }
2004
2005  [[self notificationCenter] postNotificationName: NSWorkspaceDidUnmountNotification
2006					   object: self
2007					 userInfo: userinfo];
2008
2009  /* this is system specific and we try our best
2010     and the failure of eject doesn't mean unmount failed */
2011  task = nil;
2012  if (systype == NSGNULinuxOperatingSystem)
2013    {
2014      task = [NSTask launchedTaskWithLaunchPath: @"eject"
2015				      arguments: [NSArray arrayWithObject: path]];
2016    }
2017  else if (systype == NSBSDOperatingSystem || systype == NSSolarisOperatingSystem)
2018    {
2019      NSString *mountDir;
2020
2021      // Note: it would be better to check the device, not the mount point
2022      mountDir = [path lastPathComponent];
2023      if ([mountDir rangeOfString:@"cd"].location != NSNotFound ||
2024	  [mountDir rangeOfString:@"dvd"].location != NSNotFound)
2025	{
2026	  task = [NSTask launchedTaskWithLaunchPath: @"eject"
2027					  arguments: [NSArray arrayWithObject: @"cdrom"]];
2028	}
2029      else if ([mountDir rangeOfString:@"fd"].location != NSNotFound ||
2030	  [mountDir rangeOfString:@"floppy"].location != NSNotFound)
2031	{
2032	  task = [NSTask launchedTaskWithLaunchPath: @"eject"
2033					  arguments: [NSArray arrayWithObject: @"floppy"]];
2034	}
2035    }
2036  else
2037    {
2038      NSLog(@"Don't know how to eject");
2039    }
2040  if (task != nil)
2041    {
2042      [task waitUntilExit];
2043      if ([task terminationStatus] != 0)
2044	{
2045	  NSLog(@"eject failed");
2046	}
2047    }
2048
2049  return YES;
2050}
2051
2052/*
2053 * Tracking Status Changes for Devices
2054 */
2055- (void) checkForRemovableMedia
2056{
2057  // FIXME
2058}
2059
2060- (NSArray*) mountNewRemovableMedia
2061{
2062  NSArray *removables;
2063  NSArray *mountedMedia = [self mountedRemovableMedia];
2064  NSMutableArray *willMountMedia = [NSMutableArray array];
2065  NSMutableArray *newlyMountedMedia = [NSMutableArray array];
2066  NSUInteger i;
2067
2068  /* we use the system preferences to know which ones to mount */
2069  removables = [[[NSUserDefaults standardUserDefaults] persistentDomainForName: NSGlobalDomain] objectForKey: @"GSRemovableMediaPaths"];
2070
2071  for (i = 0; i < [removables count]; i++)
2072    {
2073      NSString *removable = [removables objectAtIndex: i];
2074
2075      if ([mountedMedia containsObject: removable] == NO)
2076        {
2077          [willMountMedia addObject: removable];
2078        }
2079    }
2080
2081  for (i = 0; i < [willMountMedia count]; i++)
2082    {
2083      NSString *media = [willMountMedia objectAtIndex: i];
2084      NSTask *task = [NSTask launchedTaskWithLaunchPath: @"mount"
2085                                              arguments: [NSArray arrayWithObject: media]];
2086
2087      if (task)
2088        {
2089          [task waitUntilExit];
2090
2091          if ([task terminationStatus] == 0)
2092            {
2093              NSDictionary *userinfo = [NSDictionary dictionaryWithObject: media
2094                                                                   forKey: @"NSDevicePath"];
2095
2096              [[self notificationCenter] postNotificationName: NSWorkspaceDidMountNotification
2097                                                       object: self
2098                                                     userInfo: userinfo];
2099
2100              [newlyMountedMedia addObject: media];
2101            }
2102        }
2103    }
2104
2105  return newlyMountedMedia;
2106}
2107
2108- (NSArray*) mountedRemovableMedia
2109{
2110  NSArray		*volumes;
2111  NSMutableArray	*names;
2112  NSUInteger		count;
2113  NSUInteger		i;
2114
2115  volumes = [self mountedLocalVolumePaths];
2116  count = [volumes count];
2117  names = [NSMutableArray arrayWithCapacity: count];
2118  for (i = 0; i < count; i++)
2119    {
2120      BOOL	removableFlag;
2121      BOOL	writableFlag;
2122      BOOL	unmountableFlag;
2123      NSString	*description;
2124      NSString	*fileSystemType;
2125      NSString	*name = [volumes objectAtIndex: i];
2126
2127      if ([self getFileSystemInfoForPath: name
2128			     isRemovable: &removableFlag
2129			      isWritable: &writableFlag
2130			   isUnmountable: &unmountableFlag
2131			     description: &description
2132				    type: &fileSystemType] && removableFlag)
2133        {
2134	  [names addObject: name];
2135	}
2136    }
2137  NSDebugLog(@"mountedRemovableMedia returning names: %@", names);
2138  return names;
2139}
2140
2141- (NSArray*) mountedLocalVolumePaths
2142{
2143  NSMutableArray	*names;
2144  NSArray	        *reservedMountNames;
2145
2146  // get reserved names....
2147  reservedMountNames = [[NSUserDefaults standardUserDefaults] objectForKey: @"GSReservedMountNames"];
2148  if (reservedMountNames == nil)
2149    {
2150      reservedMountNames = [NSArray arrayWithObjects:
2151				      @"proc",@"devpts",
2152				    @"shm",@"usbdevfs",
2153				    @"devtmpfs", @"devpts",@"sysfs",
2154				    @"tmpfs",@"procbususb",
2155				    @"udev", @"pstore",
2156				    @"cgroup", nil];
2157      [[NSUserDefaults standardUserDefaults] setObject: reservedMountNames
2158						forKey: @"GSReservedMountNames"];
2159    }
2160
2161#if	defined(__MINGW32__)
2162  NSFileManager		*mgr = [NSFileManager defaultManager];
2163  unsigned		max = BUFSIZ;
2164  unichar		buf[max];
2165  unichar		*base = buf;
2166  unichar		*ptr;
2167  unichar		*end;
2168  unsigned		len;
2169
2170  names = [NSMutableArray arrayWithCapacity: 8];
2171  len = GetLogicalDriveStringsW(max-1, base);
2172  while (len >= max)
2173    {
2174      base = NSZoneMalloc(NSDefaultMallocZone(), (len+1) * sizeof(unichar));
2175      max = len;
2176      len = GetLogicalDriveStringsW(max-1, base);
2177    }
2178  for (ptr = base; *ptr != 0; ptr = end + 1)
2179    {
2180      NSString	*path;
2181
2182      end = ptr;
2183      while (*end != 0)
2184	{
2185	  end++;
2186	}
2187      len = (end - ptr);
2188      path = [mgr stringWithFileSystemRepresentation: ptr length: len];
2189      [names addObject: path];
2190    }
2191  if (base != buf)
2192    {
2193      NSZoneFree(NSDefaultMallocZone(), base);
2194    }
2195
2196#elif defined (HAVE_GETMNTINFO)
2197  NSFileManager	*mgr = [NSFileManager defaultManager];
2198  unsigned int	i, n;
2199#if defined(HAVE_STATVFS) && defined (__NetBSD__)
2200  struct statvfs *m;
2201#else
2202  struct statfs	*m;
2203#endif
2204
2205  n = getmntinfo(&m, MNT_NOWAIT);
2206  names = [NSMutableArray arrayWithCapacity: n];
2207  for (i = 0; i < n; i++)
2208    {
2209      /* NB For now assume that all local volumes are mounted from a device
2210         with an entry /dev and this is not the case for any pseudo
2211	 filesystems.
2212      */
2213      if (strncmp(m[i].f_mntfromname, "/dev/", 5) == 0)
2214        {
2215	  [names addObject:
2216		   [mgr stringWithFileSystemRepresentation: m[i].f_mntonname
2217			length: strlen(m[i].f_mntonname)]];
2218        }
2219    }
2220#elif	defined(HAVE_GETMNTENT) && defined (MNT_MEMB)
2221
2222  NSFileManager	*mgr = [NSFileManager defaultManager];
2223  FILE		*fptr = setmntent(MOUNTED_PATH, "r");
2224  struct mntent	*m;
2225
2226  names = [NSMutableArray arrayWithCapacity: 8];
2227  while ((m = getmntent(fptr)) != 0)
2228    {
2229      NSString	*path;
2230      NSString  *type;
2231
2232      path = [mgr stringWithFileSystemRepresentation: m->MNT_MEMB
2233					      length: strlen(m->MNT_MEMB)];
2234      type = [NSString stringWithCString:m->mnt_type];
2235      if ([reservedMountNames containsObject: type] == NO)
2236	{
2237	  [names addObject: path];
2238	}
2239    }
2240  endmntent(fptr);
2241#else
2242  /* we resort in parsing mtab manually and removing then reserved mount names
2243     defined in preferences GSReservedMountNames (SystemPreferences) */
2244  NSString	*mtabPath;
2245  NSString	*mtab;
2246  NSArray	*mounts;
2247  unsigned int	i;
2248
2249  // get mount table...
2250  mtabPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"GSMtabPath"];
2251  if (mtabPath == nil)
2252    {
2253      mtabPath = @"/etc/mtab";
2254    }
2255
2256  mtab = [NSString stringWithContentsOfFile: mtabPath];
2257  mounts = [mtab componentsSeparatedByString: @"\n"];
2258
2259  names = [NSMutableArray arrayWithCapacity: [mounts count]];
2260  for (i = 0; i < [mounts count]; i++)
2261    {
2262      NSString  *mount = [mounts objectAtIndex: i];
2263
2264      if ([mount length])
2265        {
2266          NSArray  *parts = [mount componentsSeparatedByString: @" "];
2267
2268          if ([parts count] >= 2)
2269            {
2270              NSString	*type = [parts objectAtIndex: 2];
2271              if ([reservedMountNames containsObject: type] == NO)
2272              {
2273	         [names addObject: [parts objectAtIndex: 1]];
2274	      }
2275           }
2276        }
2277    }
2278#endif
2279  NSDebugLog(@"mountedLocalVolumePaths returning names: %@", names);
2280  return names;
2281}
2282
2283/**
2284 * Returns the workspace notification center
2285 */
2286- (NSNotificationCenter*) notificationCenter
2287{
2288  return _workspaceCenter;
2289}
2290
2291/**
2292 * Simply makes a note that the user defaults database has changed.
2293 */
2294- (void) noteUserDefaultsChanged
2295{
2296  _userDefaultsChanged = YES;
2297}
2298
2299/**
2300 * Returns a flag to say if the defaults database has changed since
2301 * the last time this method was called.
2302 */
2303- (BOOL) userDefaultsChanged
2304{
2305  BOOL	hasChanged = _userDefaultsChanged;
2306
2307  _userDefaultsChanged = NO;
2308  return hasChanged;
2309}
2310
2311/**
2312 * Animating an Image- slides it from one point on the screen to another.
2313 */
2314- (void) slideImage: (NSImage*)image
2315	       from: (NSPoint)fromPoint
2316		 to: (NSPoint)toPoint
2317{
2318  [GSCurrentServer() slideImage: image from: fromPoint to: toPoint];
2319}
2320
2321/*
2322 * Requesting Additional Time before Power Off or Logout<br />
2323 * Returns the amount of time actually granted (which may be less than
2324 * requested).<br />
2325 * Times are measured in milliseconds.
2326 */
2327- (int) extendPowerOffBy: (int)requested
2328{
2329  id	app;
2330
2331  NS_DURING
2332    {
2333      if ((app = [self _workspaceApplication]) != nil)
2334	{
2335	  int	result;
2336
2337	  result = [app extendPowerOffBy: requested];
2338	  NS_VALRETURN(result);
2339	}
2340    }
2341  NS_HANDLER
2342    // workspace manager problem ... fall through to default code
2343  NS_ENDHANDLER
2344
2345  return 0;
2346}
2347
2348- (BOOL) filenameExtension: (NSString *)filenameExtension
2349            isValidForType: (NSString*)typeName
2350{
2351  // FIXME
2352  return [filenameExtension isEqualToString: typeName];
2353}
2354
2355- (NSString *) localizedDescriptionForType: (NSString *)typeName
2356{
2357  // FIXME
2358  return typeName;
2359}
2360
2361- (NSString *) preferredFilenameExtensionForType: (NSString *)typeName
2362{
2363  // FIXME
2364  return typeName;
2365}
2366
2367- (BOOL) type: (NSString *)firstTypeName conformsToType: (NSString *)secondTypeName
2368{
2369  // FIXME
2370  return [firstTypeName isEqualToString: secondTypeName];
2371}
2372
2373- (NSString *) typeOfFile: (NSString *)absoluteFilePath error: (NSError **)outError
2374{
2375  // FIXME
2376  return [absoluteFilePath pathExtension];
2377}
2378
2379@end
2380
2381@implementation	NSWorkspace (GNUstep)
2382
2383/**
2384 * Returns the 'best' application to open a file with the specified extension
2385 * using the given role.  If the role is nil then apps which can edit are
2386 * preferred but viewers are also acceptable.  Uses a user preferred app
2387 * or picks any good match.
2388 */
2389- (NSString*) getBestAppInRole: (NSString*)role
2390		  forExtension: (NSString*)ext
2391{
2392  NSString	*appName = nil;
2393
2394  if ([self _extension: ext role: role app: &appName] == NO)
2395    {
2396      appName = nil;
2397    }
2398  return appName;
2399}
2400
2401/**
2402 * Returns the path set for the icon matching the image by
2403 * -setBestIcon:forExtension:
2404 */
2405- (NSString*) getBestIconForExtension: (NSString*)ext
2406{
2407  NSString	*iconPath = nil;
2408
2409  if (extPreferences != nil)
2410    {
2411      NSDictionary	*inf;
2412
2413      inf = [extPreferences objectForKey: [ext lowercaseString]];
2414      if (inf != nil)
2415	{
2416	  iconPath = [inf objectForKey: @"Icon"];
2417	}
2418    }
2419  return iconPath;
2420}
2421
2422/**
2423 * Gets the applications cache (generated by the make_services tool)
2424 * and looks up the special entry that contains a dictionary of all
2425 * file extensions recognised by GNUstep applications.  Then finds
2426 * the dictionary of applications that can handle our file and
2427 * returns it.
2428 */
2429- (NSDictionary*) infoForExtension: (NSString*)ext
2430{
2431  NSDictionary  *map;
2432
2433  ext = [ext lowercaseString];
2434  map = [applications objectForKey: @"GSExtensionsMap"];
2435  return [map objectForKey: ext];
2436}
2437
2438/**
2439 * Returns the application bundle for the named application. Accepts
2440 * either a full path to an app or just the name. The extension (.app,
2441 * .debug, .profile) is optional, but if provided it will be used.<br />
2442 * Returns nil if the specified app does not exist as requested.
2443 */
2444- (NSBundle*) bundleForApp: (NSString*)appName
2445{
2446  if ([appName length] == 0)
2447    {
2448      return nil;
2449    }
2450  if ([[appName lastPathComponent] isEqual: appName]) // it's a name
2451    {
2452      appName = [self fullPathForApplication: appName];
2453    }
2454  else
2455    {
2456      NSFileManager	*fm;
2457      NSString		*ext;
2458      BOOL		flag;
2459
2460      fm = [NSFileManager defaultManager];
2461      ext = [appName pathExtension];
2462      if ([ext length] == 0) // no extension, let's find one
2463	{
2464	  NSString	*path;
2465
2466	  path = [appName stringByAppendingPathExtension: @"app"];
2467	  if ([fm fileExistsAtPath: path isDirectory: &flag] == NO
2468	    || flag == NO)
2469	    {
2470	      path = [appName stringByAppendingPathExtension: @"debug"];
2471	      if ([fm fileExistsAtPath: path isDirectory: &flag] == NO
2472		|| flag == NO)
2473		{
2474		  path = [appName stringByAppendingPathExtension: @"profile"];
2475		}
2476	    }
2477	  appName = path;
2478	}
2479      if ([fm fileExistsAtPath: appName isDirectory: &flag] == NO
2480	|| flag == NO)
2481	{
2482	  appName = nil;
2483	}
2484    }
2485  if (appName == nil)
2486    {
2487      return nil;
2488    }
2489  return [NSBundle bundleWithPath: appName];
2490}
2491
2492/**
2493 * Returns the application icon for the given app.
2494 * Or null if none defined or appName is not a valid application name.
2495 */
2496- (NSImage*) appIconForApp: (NSString*)appName
2497{
2498  NSBundle *bundle;
2499  NSImage *image = nil;
2500  NSFileManager *mgr = [NSFileManager defaultManager];
2501  NSString *iconPath = nil;
2502  NSString *fullPath;
2503
2504  fullPath = [self fullPathForApplication: appName];
2505  bundle = [self bundleForApp: fullPath];
2506  if (bundle == nil)
2507    {
2508      return nil;
2509    }
2510
2511  iconPath = [[bundle infoDictionary] objectForKey: @"NSIcon"];
2512  if (iconPath == nil)
2513    {
2514      /*
2515       * Try the CFBundleIconFile property.
2516       */
2517      iconPath = [[bundle infoDictionary] objectForKey: @"CFBundleIconFile"];
2518    }
2519
2520  if (iconPath && [iconPath isAbsolutePath] == NO)
2521    {
2522      NSString *file = iconPath;
2523
2524      iconPath = [bundle pathForImageResource: file];
2525
2526      /*
2527       * If there is no icon in the Resources of the app, try
2528       * looking directly in the app wrapper.
2529       */
2530      if (iconPath == nil)
2531        {
2532          iconPath = [fullPath stringByAppendingPathComponent: file];
2533          if ([mgr isReadableFileAtPath: iconPath] == NO)
2534            {
2535              iconPath = nil;
2536            }
2537        }
2538    }
2539
2540  /*
2541   * If there is no icon specified in the Info.plist for app
2542   * try 'wrapper/app.png'
2543   */
2544  if (iconPath == nil)
2545    {
2546      NSString *str;
2547
2548      str = [fullPath lastPathComponent];
2549      str = [str stringByDeletingPathExtension];
2550      iconPath = [fullPath stringByAppendingPathComponent: str];
2551      iconPath = [iconPath stringByAppendingPathExtension: @"png"];
2552      if ([mgr isReadableFileAtPath: iconPath] == NO)
2553        {
2554	  iconPath = [iconPath stringByAppendingPathExtension: @"tiff"];
2555	  if ([mgr isReadableFileAtPath: iconPath] == NO)
2556	    {
2557	      iconPath = [iconPath stringByAppendingPathExtension: @"icns"];
2558	      if ([mgr isReadableFileAtPath: iconPath] == NO)
2559		{
2560		  iconPath = nil;
2561		}
2562	    }
2563        }
2564    }
2565
2566  if (iconPath != nil)
2567    {
2568      image = [self _saveImageFor: iconPath];
2569    }
2570
2571  return image;
2572}
2573
2574/**
2575 * Requires the path to an application wrapper as an argument, and returns
2576 * the full path to the executable.
2577 */
2578- (NSString*) locateApplicationBinary: (NSString*)appName
2579{
2580  NSString	*path;
2581  NSString	*file;
2582  NSBundle	*bundle = [self bundleForApp: appName];
2583
2584  if (bundle == nil)
2585    {
2586      return nil;
2587    }
2588  path = [bundle bundlePath];
2589  file = [[bundle infoDictionary] objectForKey: @"NSExecutable"];
2590
2591  if (file == nil)
2592    {
2593      /*
2594       * If there is no executable specified in the info property-list, then
2595       * we expect the executable to reside within the app wrapper and to
2596       * have the same name as the app wrapper but without the extension.
2597       */
2598      file = [path lastPathComponent];
2599      file = [file stringByDeletingPathExtension];
2600      path = [path stringByAppendingPathComponent: file];
2601    }
2602  else
2603    {
2604      /*
2605       * If there is an executable specified in the info property-list, then
2606       * it can be either an absolute path, or a path relative to the app
2607       * wrapper, so we make sure we end up with an absolute path to return.
2608       */
2609      if ([file isAbsolutePath] == YES)
2610	{
2611	  path = file;
2612	}
2613      else
2614	{
2615	  path = [path stringByAppendingPathComponent: file];
2616	}
2617    }
2618
2619  return path;
2620}
2621
2622/**
2623 * Sets up a user preference  for which app should be used to open files
2624 * of the specified extension.
2625 */
2626- (void) setBestApp: (NSString*)appName
2627	     inRole: (NSString*)role
2628       forExtension: (NSString*)ext
2629{
2630  NSMutableDictionary	*map;
2631  NSMutableDictionary	*inf;
2632  NSData		*data;
2633
2634  ext = [ext lowercaseString];
2635  if (extPreferences != nil)
2636    map = [extPreferences mutableCopy];
2637  else
2638    map = [NSMutableDictionary new];
2639
2640  inf = [[map objectForKey: ext] mutableCopy];
2641  if (inf == nil)
2642    {
2643      inf = [NSMutableDictionary new];
2644    }
2645  if (appName == nil)
2646    {
2647      if (role == nil)
2648	{
2649	  NSString	*iconPath = [inf objectForKey: @"Icon"];
2650
2651	  RETAIN(iconPath);
2652	  [inf removeAllObjects];
2653	  if (iconPath)
2654	    {
2655	      [inf setObject: iconPath forKey: @"Icon"];
2656	      RELEASE(iconPath);
2657	    }
2658	}
2659      else
2660	{
2661	  [inf removeObjectForKey: role];
2662	}
2663    }
2664  else
2665    {
2666      [inf setObject: appName forKey: (role ? (id)role : (id)@"Editor")];
2667    }
2668  [map setObject: inf forKey: ext];
2669  RELEASE(inf);
2670  RELEASE(extPreferences);
2671  extPreferences = map;
2672  data = [NSSerializer serializePropertyList: extPreferences];
2673  if ([data writeToFile: extPrefPath atomically: YES])
2674    {
2675      [_workspaceCenter postNotificationName: GSWorkspacePreferencesChanged
2676				      object: self];
2677    }
2678  else
2679    {
2680      NSLog(@"Update %@ of failed", extPrefPath);
2681    }
2682}
2683
2684/**
2685 * Sets up a user preference for which icon should be used to
2686 * represent the specified file extension.
2687 */
2688- (void) setBestIcon: (NSString*)iconPath forExtension: (NSString*)ext
2689{
2690  NSMutableDictionary	*map;
2691  NSMutableDictionary	*inf;
2692  NSData		*data;
2693
2694  ext = [ext lowercaseString];
2695  if (extPreferences != nil)
2696    map = [extPreferences mutableCopy];
2697  else
2698    map = [NSMutableDictionary new];
2699
2700  inf = [[map objectForKey: ext] mutableCopy];
2701  if (inf == nil)
2702    inf = [NSMutableDictionary new];
2703  if (iconPath)
2704    [inf setObject: iconPath forKey: @"Icon"];
2705  else
2706    [inf removeObjectForKey: @"Icon"];
2707  [map setObject: inf forKey: ext];
2708  RELEASE(inf);
2709  RELEASE(extPreferences);
2710  extPreferences = map;
2711  data = [NSSerializer serializePropertyList: extPreferences];
2712  if ([data writeToFile: extPrefPath atomically: YES])
2713    {
2714      [_workspaceCenter postNotificationName: GSWorkspacePreferencesChanged
2715				      object: self];
2716    }
2717  else
2718    {
2719      NSLog(@"Update %@ of failed", extPrefPath);
2720    }
2721}
2722
2723/**
2724 * Gets the applications cache (generated by the make_services tool)
2725 * and looks up the special entry that contains a dictionary of all
2726 * URL schemes recognised by GNUstep applications.  Then finds the
2727 * dictionary of applications that can handle our scheme and returns
2728 * it.
2729 */
2730- (NSDictionary*) infoForScheme: (NSString*)scheme
2731{
2732  NSDictionary  *map;
2733
2734  scheme = [scheme lowercaseString];
2735  map = [applications objectForKey: @"GSSchemesMap"];
2736  return [map objectForKey: scheme];
2737}
2738
2739/**
2740 * Returns the 'best' application to open a file with the specified URL
2741 * scheme using the given role.  If the role is nil then apps which can
2742 * edit are preferred but viewers are also acceptable.  Uses a user preferred
2743 * app or picks any good match.
2744 */
2745- (NSString*) getBestAppInRole: (NSString*)role
2746		     forScheme: (NSString*)scheme
2747{
2748  NSString	*appName = nil;
2749
2750  if ([self _scheme: scheme role: role app: &appName] == NO)
2751    {
2752      appName = nil;
2753    }
2754  return appName;
2755}
2756
2757/**
2758 * Sets up a user preference for which app should be used to open files
2759 * of the specified URL scheme
2760 */
2761- (void) setBestApp: (NSString*)appName
2762	     inRole: (NSString*)role
2763	  forScheme: (NSString*)scheme
2764{
2765  NSMutableDictionary	*map;
2766  NSMutableDictionary	*inf;
2767  NSData		*data;
2768
2769  scheme = [scheme lowercaseString];
2770  if (urlPreferences != nil)
2771    map = [urlPreferences mutableCopy];
2772  else
2773    map = [NSMutableDictionary new];
2774
2775  inf = [[map objectForKey: scheme] mutableCopy];
2776  if (inf == nil)
2777    {
2778      inf = [NSMutableDictionary new];
2779    }
2780  if (appName == nil)
2781    {
2782      if (role == nil)
2783	{
2784	  NSString	*iconPath = [inf objectForKey: @"Icon"];
2785
2786	  RETAIN(iconPath);
2787	  [inf removeAllObjects];
2788	  if (iconPath)
2789	    {
2790	      [inf setObject: iconPath forKey: @"Icon"];
2791	      RELEASE(iconPath);
2792	    }
2793	}
2794      else
2795	{
2796	  [inf removeObjectForKey: role];
2797	}
2798    }
2799  else
2800    {
2801      [inf setObject: appName forKey: (role ? (id)role : (id)@"Editor")];
2802    }
2803  [map setObject: inf forKey: scheme];
2804  RELEASE(inf);
2805  RELEASE(urlPreferences);
2806  urlPreferences = map;
2807  data = [NSSerializer serializePropertyList: urlPreferences];
2808  if ([data writeToFile: urlPrefPath atomically: YES])
2809    {
2810      [_workspaceCenter postNotificationName: GSWorkspacePreferencesChanged
2811				      object: self];
2812    }
2813  else
2814    {
2815      NSLog(@"Update %@ of failed", urlPrefPath);
2816    }
2817}
2818
2819@end
2820
2821@implementation NSWorkspace (Private)
2822
2823- (NSImage*) _extIconForApp: (NSString*)appName info: (NSDictionary*)extInfo
2824{
2825  NSDictionary	*typeInfo = [extInfo objectForKey: appName];
2826  NSString	*file = [typeInfo objectForKey: @"NSIcon"];
2827
2828  /*
2829   * If the NSIcon entry isn't there and the CFBundle entries are,
2830   * get the first icon in the list if it's an array, or assign
2831   * the icon to file if it's a string.
2832   *
2833   * FIXME: CFBundleTypeExtensions/IconFile can be arrays which assign
2834   * multiple types to icons.  This needs to be handled eventually.
2835   */
2836  if (file == nil)
2837    {
2838      id icon = [typeInfo objectForKey: @"CFBundleTypeIconFile"];
2839      if ([icon isKindOfClass: [NSArray class]])
2840	{
2841	  if ([icon count])
2842	    {
2843	      file = [icon objectAtIndex: 0];
2844	    }
2845	}
2846      else
2847	{
2848	  file = icon;
2849	}
2850    }
2851
2852  if (file && [file length] != 0)
2853    {
2854      if ([file isAbsolutePath] == NO)
2855	{
2856	  NSString *iconPath;
2857	  NSBundle *bundle;
2858
2859	  bundle = [self bundleForApp: appName];
2860	  iconPath = [bundle pathForImageResource: file];
2861	  /*
2862	   * If the icon is not in the Resources of the app, try looking
2863	   * directly in the app wrapper.
2864	   */
2865	  if (iconPath == nil)
2866	    {
2867	      iconPath = [[bundle bundlePath]
2868		stringByAppendingPathComponent: file];
2869	    }
2870	  file = iconPath;
2871	}
2872      if ([[NSFileManager defaultManager] isReadableFileAtPath: file] == YES)
2873	{
2874	  return [self _saveImageFor: file];
2875	}
2876    }
2877  return nil;
2878}
2879
2880/** Returns the default icon to display for a file */
2881- (NSImage*) unknownFiletypeImage
2882{
2883  static NSImage *image = nil;
2884
2885  if (image == nil)
2886    {
2887      image = RETAIN([NSImage _standardImageWithName: @"Unknown"]);
2888    }
2889
2890  return image;
2891}
2892
2893/** Try to create the image in an exception handling context */
2894- (NSImage*) _saveImageFor: (NSString*)iconPath
2895{
2896  NSImage *tmp = nil;
2897
2898  NS_DURING
2899    {
2900      tmp = [[NSImage alloc] initWithContentsOfFile: iconPath];
2901      if (tmp != nil)
2902        {
2903	  AUTORELEASE(tmp);
2904	}
2905    }
2906  NS_HANDLER
2907    {
2908      NSLog(@"BAD TIFF FILE '%@'", iconPath);
2909    }
2910  NS_ENDHANDLER
2911
2912  return tmp;
2913}
2914
2915/** Returns the freedesktop thumbnail file name for a given file name */
2916- (NSString*) thumbnailForFile: (NSString *)file
2917{
2918  NSString *absolute;
2919  NSString *digest;
2920  NSString *thumbnail;
2921
2922  absolute = [[NSURL fileURLWithPath: [file stringByStandardizingPath]]
2923		 absoluteString];
2924  /* This compensates for a feature we have in NSURL, that is there to have
2925   * MacOSX compatibility.
2926   */
2927  if ([absolute hasPrefix:  @"file://localhost/"])
2928    {
2929      absolute = [@"file:///" stringByAppendingString:
2930		       [absolute substringWithRange:
2931				     NSMakeRange(17, [absolute length] - 17)]];
2932    }
2933
2934  // FIXME: Not sure which encoding to use here.
2935  digest = [[[[absolute dataUsingEncoding: NSASCIIStringEncoding]
2936		 md5Digest] hexadecimalRepresentation] lowercaseString];
2937  thumbnail = [@"~/.thumbnails/normal" stringByAppendingPathComponent:
2938		    [digest stringByAppendingPathExtension: @"png"]];
2939
2940  return [thumbnail stringByStandardizingPath];
2941}
2942
2943- (NSImage*) _iconForExtension: (NSString*)ext
2944{
2945  NSImage	*icon = nil;
2946
2947  if (ext == nil || [ext isEqualToString: @""])
2948    {
2949      return nil;
2950    }
2951  /*
2952   * extensions are case-insensitive - convert to lowercase.
2953   */
2954  ext = [ext lowercaseString];
2955  if ((icon = [_iconMap objectForKey: ext]) == nil)
2956    {
2957      NSDictionary	*prefs;
2958      NSDictionary	*extInfo;
2959      NSString		*iconPath;
2960
2961      /*
2962       * If there is a user-specified preference for an image -
2963       * try to use that one.
2964       */
2965      prefs = [extPreferences objectForKey: ext];
2966      iconPath = [prefs objectForKey: @"Icon"];
2967      if (iconPath)
2968	{
2969	  icon = [self _saveImageFor: iconPath];
2970	}
2971
2972      if (icon == nil && (extInfo = [self infoForExtension: ext]) != nil)
2973	{
2974	  NSString	*appName;
2975
2976	  /*
2977	   * If there are any application preferences given, try to use the
2978	   * icon for this file that is used by the preferred app.
2979	   */
2980	  if (prefs)
2981	    {
2982	      if ((appName = [prefs objectForKey: @"Editor"]) != nil)
2983		{
2984		  icon = [self _extIconForApp: appName info: extInfo];
2985		}
2986	      if (icon == nil
2987		&& (appName = [prefs objectForKey: @"Viewer"]) != nil)
2988		{
2989		  icon = [self _extIconForApp: appName info: extInfo];
2990		}
2991	    }
2992
2993	  if (icon == nil)
2994	    {
2995	      NSEnumerator	*enumerator;
2996
2997	      /*
2998	       * Still no icon - try all the apps that handle this file
2999	       * extension.
3000	       */
3001	      enumerator = [extInfo keyEnumerator];
3002	      while (icon == nil && (appName = [enumerator nextObject]) != nil)
3003		{
3004		  icon = [self _extIconForApp: appName info: extInfo];
3005		}
3006	    }
3007	}
3008
3009      /*
3010       * Nothing found at all - use the unknowntype icon.
3011       */
3012      if (icon == nil)
3013	{
3014	  if ([ext isEqualToString: @"app"] == YES
3015	    || [ext isEqualToString: @"debug"] == YES
3016	    || [ext isEqualToString: @"profile"] == YES)
3017	    {
3018	      if (unknownApplication == nil)
3019		{
3020		  unknownApplication = RETAIN([NSImage _standardImageWithName:
3021		    @"UnknownApplication"]);
3022		}
3023	      icon = unknownApplication;
3024	    }
3025	  else
3026	    {
3027	      icon = [self unknownFiletypeImage];
3028	    }
3029	}
3030
3031      /*
3032       * Set the icon in the cache for next time.
3033       */
3034      if (icon != nil)
3035	{
3036	  [_iconMap setObject: icon forKey: ext];
3037	}
3038    }
3039  return icon;
3040}
3041
3042- (BOOL) _extension: (NSString*)ext
3043               role: (NSString*)role
3044	        app: (NSString**)app
3045{
3046  NSEnumerator	*enumerator;
3047  NSString      *appName = nil;
3048  NSDictionary	*apps = [self infoForExtension: ext];
3049  NSDictionary	*prefs;
3050  NSDictionary	*info;
3051
3052  ext = [ext lowercaseString];
3053
3054  /*
3055   *	Look for the name of the preferred app in this role.
3056   *	A 'nil' roll is a wildcard - find the preferred Editor or Viewer.
3057   */
3058  prefs = [extPreferences objectForKey: ext];
3059  if (role == nil || [role isEqualToString: @"Editor"])
3060    {
3061      appName = [prefs objectForKey: @"Editor"];
3062      if (appName != nil)
3063	{
3064	  info = [apps objectForKey: appName];
3065	  if (info != nil)
3066	    {
3067	      if (app != 0)
3068		{
3069		  *app = appName;
3070		}
3071	      return YES;
3072	    }
3073	  else if ([self locateApplicationBinary: appName] != nil)
3074	    {
3075	      /*
3076	       * Return the preferred application even though it doesn't
3077	       * say it opens this type of file ... preferences overrule.
3078	       */
3079	      if (app != 0)
3080		{
3081		  *app = appName;
3082		}
3083	      return YES;
3084	    }
3085	}
3086    }
3087  if (role == nil || [role isEqualToString: @"Viewer"])
3088    {
3089      appName = [prefs objectForKey: @"Viewer"];
3090      if (appName != nil)
3091	{
3092	  info = [apps objectForKey: appName];
3093	  if (info != nil)
3094	    {
3095	      if (app != 0)
3096		{
3097		  *app = appName;
3098		}
3099	      return YES;
3100	    }
3101	  else if ([self locateApplicationBinary: appName] != nil)
3102	    {
3103	      /*
3104	       * Return the preferred application even though it doesn't
3105	       * say it opens this type of file ... preferences overrule.
3106	       */
3107	      if (app != 0)
3108		{
3109		  *app = appName;
3110		}
3111	      return YES;
3112	    }
3113	}
3114    }
3115
3116  /*
3117   * Go through the dictionary of apps that know about this file type and
3118   * determine the best application to open the file by examining the
3119   * type information for each app.
3120   * The 'NSRole' field specifies what the app can do with the file - if it
3121   * is missing, we assume an 'Editor' role.
3122   */
3123  if (apps == nil || [apps count] == 0)
3124    {
3125      return NO;
3126    }
3127  enumerator = [apps keyEnumerator];
3128
3129  if (role == nil)
3130    {
3131      BOOL	found = NO;
3132
3133      /*
3134       * If the requested role is 'nil', we can accept an app that is either
3135       * an Editor (preferred) or a Viewer, or unknown.
3136       */
3137      while ((appName = [enumerator nextObject]) != nil)
3138	{
3139	  NSString	*str;
3140
3141	  info = [apps objectForKey: appName];
3142	  str = [info objectForKey: @"NSRole"];
3143	  /* NB. If str is nil or an empty string, there is no role set,
3144	   * and we treat this as an Editor since the role is unrestricted.
3145	   */
3146	  if ([str length] == 0 || [str isEqualToString: @"Editor"])
3147	    {
3148	      if (app != 0)
3149		{
3150		  *app = appName;
3151		}
3152	      return YES;
3153	    }
3154	  if ([str isEqualToString: @"Viewer"])
3155	    {
3156	      if (app != 0)
3157		{
3158		  *app = appName;
3159		}
3160	      found = YES;
3161	    }
3162	}
3163      return found;
3164    }
3165  else
3166    {
3167      while ((appName = [enumerator nextObject]) != nil)
3168	{
3169	  NSString	*str;
3170
3171	  info = [apps objectForKey: appName];
3172	  str = [info objectForKey: @"NSRole"];
3173	  if ((str == nil && [role isEqualToString: @"Editor"])
3174	    || [str isEqualToString: role])
3175	    {
3176	      if (app != 0)
3177		{
3178		  *app = appName;
3179		}
3180	      return YES;
3181	    }
3182	}
3183      return NO;
3184    }
3185}
3186
3187- (BOOL) _scheme: (NSString*)scheme
3188	    role: (NSString*)role
3189	     app: (NSString**)app
3190{
3191  NSEnumerator	*enumerator;
3192  NSString      *appName = nil;
3193  NSDictionary	*apps = [self infoForScheme: scheme];
3194  NSDictionary	*prefs;
3195  NSDictionary	*info;
3196
3197  scheme = [scheme lowercaseString];
3198
3199  /*
3200   *	Look for the name of the preferred app in this role.
3201   *	A 'nil' roll is a wildcard - find the preferred Editor or Viewer.
3202   */
3203  prefs = [urlPreferences objectForKey: scheme];
3204  if (role == nil || [role isEqualToString: @"Editor"])
3205    {
3206      appName = [prefs objectForKey: @"Editor"];
3207      if (appName != nil)
3208	{
3209	  info = [apps objectForKey: appName];
3210	  if (info != nil)
3211	    {
3212	      if (app != 0)
3213		{
3214		  *app = appName;
3215		}
3216	      return YES;
3217	    }
3218	  else if ([self locateApplicationBinary: appName] != nil)
3219	    {
3220	      /*
3221	       * Return the preferred application even though it doesn't
3222	       * say it opens this type of file ... preferences overrule.
3223	       */
3224	      if (app != 0)
3225		{
3226		  *app = appName;
3227		}
3228	      return YES;
3229	    }
3230	}
3231    }
3232  if (role == nil || [role isEqualToString: @"Viewer"])
3233    {
3234      appName = [prefs objectForKey: @"Viewer"];
3235      if (appName != nil)
3236	{
3237	  info = [apps objectForKey: appName];
3238	  if (info != nil)
3239	    {
3240	      if (app != 0)
3241		{
3242		  *app = appName;
3243		}
3244	      return YES;
3245	    }
3246	  else if ([self locateApplicationBinary: appName] != nil)
3247	    {
3248	      /*
3249	       * Return the preferred application even though it doesn't
3250	       * say it opens this type of file ... preferences overrule.
3251	       */
3252	      if (app != 0)
3253		{
3254		  *app = appName;
3255		}
3256	      return YES;
3257	    }
3258	}
3259    }
3260
3261  /*
3262   * Go through the dictionary of apps that know about this file type and
3263   * determine the best application to open the file by examining the
3264   * type information for each app.
3265   * The 'NSRole' field specifies what the app can do with the file - if it
3266   * is missing, we assume an 'Editor' role.
3267   */
3268  if (apps == nil || [apps count] == 0)
3269    {
3270      return NO;
3271    }
3272  enumerator = [apps keyEnumerator];
3273
3274  if (role == nil)
3275    {
3276      BOOL	found = NO;
3277
3278      /*
3279       * If the requested role is 'nil', we can accept an app that is either
3280       * an Editor (preferred) or a Viewer, or unknown.
3281       */
3282      while ((appName = [enumerator nextObject]) != nil)
3283	{
3284	  NSString	*str;
3285
3286	  info = [apps objectForKey: appName];
3287	  str = [info objectForKey: @"NSRole"];
3288	  /* NB. If str is nil or an empty string, there is no role set,
3289	   * and we treat this as an Editor since the role is unrestricted.
3290	   */
3291	  if ([str length] == 0 || [str isEqualToString: @"Editor"])
3292	    {
3293	      if (app != 0)
3294		{
3295		  *app = appName;
3296		}
3297	      return YES;
3298	    }
3299	  if ([str isEqualToString: @"Viewer"])
3300	    {
3301	      if (app != 0)
3302		{
3303		  *app = appName;
3304		}
3305	      found = YES;
3306	    }
3307	}
3308      return found;
3309    }
3310  else
3311    {
3312      while ((appName = [enumerator nextObject]) != nil)
3313	{
3314	  NSString	*str;
3315
3316	  info = [apps objectForKey: appName];
3317	  str = [info objectForKey: @"NSRole"];
3318	  if ((str == nil && [role isEqualToString: @"Editor"])
3319	    || [str isEqualToString: role])
3320	    {
3321	      if (app != 0)
3322		{
3323		  *app = appName;
3324		}
3325	      return YES;
3326	    }
3327	}
3328      return NO;
3329    }
3330}
3331
3332- (void) _workspacePreferencesChanged: (NSNotification *)aNotification
3333{
3334  /* FIXME reload only those preferences that really were changed
3335   * TODO  add a user info to aNotification, which includes a bitmask
3336   *       denoting the updated preference files.
3337   */
3338  NSFileManager		*mgr = [NSFileManager defaultManager];
3339  NSData		*data;
3340  NSDictionary		*dict;
3341
3342  if ([mgr isReadableFileAtPath: extPrefPath] == YES)
3343    {
3344      data = [NSData dataWithContentsOfFile: extPrefPath];
3345      if (data)
3346	{
3347	  dict = [NSDeserializer deserializePropertyListFromData: data
3348					       mutableContainers: NO];
3349	  ASSIGN(extPreferences, dict);
3350	}
3351    }
3352
3353  if ([mgr isReadableFileAtPath: urlPrefPath] == YES)
3354    {
3355      data = [NSData dataWithContentsOfFile: urlPrefPath];
3356      if (data)
3357	{
3358	  dict = [NSDeserializer deserializePropertyListFromData: data
3359					       mutableContainers: NO];
3360	  ASSIGN(urlPreferences, dict);
3361	}
3362    }
3363
3364  if ([mgr isReadableFileAtPath: appListPath] == YES)
3365    {
3366      data = [NSData dataWithContentsOfFile: appListPath];
3367      if (data)
3368	{
3369	  dict = [NSDeserializer deserializePropertyListFromData: data
3370					       mutableContainers: NO];
3371	  ASSIGN(applications, dict);
3372	}
3373    }
3374  /*
3375   *	Invalidate the cache of icons for file extensions.
3376   */
3377  [_iconMap removeAllObjects];
3378}
3379
3380
3381/**
3382 * Launch an application locally (ie without reference to the workspace
3383 * manager application).  We should only call this method when we want
3384 * the application launched by this process, either because what we are
3385 * launching IS the workspace manager, or because we have tried to get
3386 * the workspace manager to do the job and been unable to do so.
3387 */
3388- (BOOL) _launchApplication: (NSString*)appName
3389		  arguments: (NSArray*)args
3390{
3391  NSTask	*task;
3392  NSString	*path;
3393  NSDictionary	*userinfo;
3394  NSString	*host;
3395
3396  path = [self locateApplicationBinary: appName];
3397  if (path == nil)
3398    {
3399      return NO;
3400    }
3401
3402  /*
3403   * Try to ensure that apps we launch display in this workspace
3404   * ie they have the same -NSHost specification.
3405   */
3406  host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"];
3407  if (host != nil)
3408    {
3409      NSHost	*h;
3410
3411      h = [NSHost hostWithName: host];
3412      if ([h isEqual: [NSHost currentHost]] == NO)
3413	{
3414	  if ([args containsObject: @"-NSHost"] == NO)
3415	    {
3416	      NSMutableArray	*a;
3417
3418	      if (args == nil)
3419		{
3420		  a = [NSMutableArray arrayWithCapacity: 2];
3421		}
3422	      else
3423		{
3424		  a = AUTORELEASE([args mutableCopy]);
3425		}
3426	      [a insertObject: @"-NSHost" atIndex: 0];
3427	      [a insertObject: host atIndex: 1];
3428	      args = a;
3429	    }
3430	}
3431    }
3432  /*
3433   * App being launched, send
3434   * NSWorkspaceWillLaunchApplicationNotification
3435   */
3436  userinfo = [NSDictionary dictionaryWithObjectsAndKeys:
3437    [[appName lastPathComponent] stringByDeletingPathExtension],
3438			   @"NSApplicationName",
3439    appName, @"NSApplicationPath",
3440    nil];
3441  [_workspaceCenter
3442    postNotificationName: NSWorkspaceWillLaunchApplicationNotification
3443    object: self
3444    userInfo: userinfo];
3445
3446  task = [NSTask launchedTaskWithLaunchPath: path arguments: args];
3447  if (task == nil)
3448    {
3449      return NO;
3450    }
3451  /*
3452   * The NSWorkspaceDidLaunchApplicationNotification will be
3453   * sent by the started application itself.
3454   */
3455  [_launched setObject: task forKey: appName];
3456  return YES;
3457}
3458
3459- (id) _connectApplication: (NSString*)appName
3460{
3461  NSTimeInterval        replyTimeout = 0.0;
3462  NSTimeInterval        requestTimeout = 0.0;
3463  NSString	*host;
3464  NSString	*port;
3465  NSDate	*when = nil;
3466  NSConnection  *conn = nil;
3467  id		app = nil;
3468
3469  while (app == nil)
3470    {
3471      host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"];
3472      if (host == nil)
3473	{
3474	  host = @"";
3475	}
3476      else
3477	{
3478	  NSHost	*h;
3479
3480	  h = [NSHost hostWithName: host];
3481	  if ([h isEqual: [NSHost currentHost]] == YES)
3482	    {
3483	      host = @"";
3484	    }
3485	}
3486      port = [[appName lastPathComponent] stringByDeletingPathExtension];
3487      /*
3488       *	Try to contact a running application.
3489       */
3490      NS_DURING
3491	{
3492          conn = [NSConnection connectionWithRegisteredName: port host: host];
3493          requestTimeout = [conn requestTimeout];
3494          [conn setRequestTimeout: 5.0];
3495          replyTimeout = [conn replyTimeout];
3496          [conn setReplyTimeout: 5.0];
3497	  app = [conn rootProxy];
3498	}
3499      NS_HANDLER
3500	{
3501	  /* Fatal error in DO	*/
3502          conn = nil;
3503	  app = nil;
3504	}
3505      NS_ENDHANDLER
3506
3507      if (app == nil)
3508	{
3509	  NSTask	*task = [_launched objectForKey: appName];
3510	  NSDate	*limit;
3511
3512	  if (task == nil || [task isRunning] == NO)
3513	    {
3514	      if (task != nil)	// Not running
3515		{
3516		  [_launched removeObjectForKey: appName];
3517		}
3518	      break;		// Need to launch the app
3519	    }
3520
3521	  if (when == nil)
3522	    {
3523	      when = [[NSDate alloc] init];
3524	    }
3525	  else if ([when timeIntervalSinceNow] < -5.0)
3526	    {
3527	      int		result;
3528
3529	      DESTROY(when);
3530              result = NSRunAlertPanel(appName,
3531                @"Application seems to have hung",
3532                @"Continue", @"Terminate", @"Wait");
3533
3534	      if (result == NSAlertDefaultReturn)
3535		{
3536		  break;		// Finished without app
3537		}
3538	      else if (result == NSAlertOtherReturn)
3539		{
3540		  // Continue to wait for app startup.
3541		}
3542	      else
3543		{
3544		  [task terminate];
3545		  [_launched removeObjectForKey: appName];
3546		  break;		// Terminate hung app
3547		}
3548	    }
3549
3550	  // Give it another 0.5 of a second to start up.
3551	  limit = [[NSDate alloc] initWithTimeIntervalSinceNow: 0.5];
3552	  [[NSRunLoop currentRunLoop] runUntilDate: limit];
3553	  RELEASE(limit);
3554	}
3555    }
3556  if (conn != nil)
3557    {
3558      /* Use original timeouts
3559       */
3560      [conn setRequestTimeout: requestTimeout];
3561      [conn setReplyTimeout: replyTimeout];
3562    }
3563  TEST_RELEASE(when);
3564  return app;
3565}
3566
3567- (id) _workspaceApplication
3568{
3569  static NSUserDefaults		*defs = nil;
3570  static GSServicesManager	*smgr = nil;
3571  NSString			*appName;
3572  NSString			*myName;
3573  id				app;
3574
3575  if (defs == nil)
3576    {
3577      defs = RETAIN([NSUserDefaults standardUserDefaults]);
3578    }
3579  if (smgr == nil)
3580    {
3581      smgr = RETAIN([GSServicesManager manager]);
3582    }
3583  /* What Workspace application? */
3584  appName = [defs stringForKey: @"GSWorkspaceApplication"];
3585  if (appName == nil)
3586    {
3587      appName = @"GWorkspace";
3588    }
3589  /*
3590   * If this app is the workspace app, there is no sense contacting
3591   * it as it would cause recursion ... so we return nil.
3592   */
3593  myName = [smgr port];
3594  if ([appName isEqual: myName] == YES)
3595    {
3596      return nil;
3597    }
3598
3599  app = [self _connectApplication: appName];
3600  if (app == nil)
3601    {
3602      NSString	*host;
3603
3604      host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"];
3605      if (host == nil)
3606	{
3607	  host = @"";
3608	}
3609      else
3610	{
3611	  NSHost	*h;
3612
3613	  h = [NSHost hostWithName: host];
3614	  if ([h isEqual: [NSHost currentHost]] == YES)
3615	    {
3616	      host = @"";
3617	    }
3618	}
3619      /**
3620       * We can only launch a workspace app if we are displaying to the
3621       * local host (since if we are displaying on another host we want
3622       * to to talk to the workspace app on that host too).
3623       */
3624      if ([host isEqual: @""] == YES)
3625	{
3626	  if ([self _launchApplication: appName
3627			     arguments: nil] == YES)
3628	    {
3629	      app = [self _connectApplication: appName];
3630	    }
3631	}
3632    }
3633
3634  return app;
3635}
3636
3637@end
3638