/* WorkspaceApplication.m
 *  
 * Copyright (C) 2006 Free Software Foundation, Inc.
 *
 * Author: Enrico Sersale <enrico@imago.ro>
 * Date: January 2006
 *
 * This file is part of the GNUstep GWorkspace application
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA.
 */

#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
#include <math.h>
#include "GWorkspace.h"
#include "GWFunctions.h"
#include "FSNodeRep.h"
#include "FSNFunctions.h"
#include "GWorkspace.h"
#include "GWDesktopManager.h"
#include "Dock.h"
#include "GWViewersManager.h"
#include "Operation.h"
#include "StartAppWin.h"
#include "GNUstep.h"

@implementation GWorkspace (WorkspaceApplication)

- (BOOL)performFileOperation:(NSString *)operation 
                      source:(NSString *)source 
                 destination:(NSString *)destination 
                       files:(NSArray *)files 
                         tag:(int *)tag
{
  if (loggingout == NO) {
    NSMutableDictionary *opdict = [NSMutableDictionary dictionary];

    [opdict setObject: operation forKey: @"operation"];
    [opdict setObject: source forKey: @"source"];
    [opdict setObject: destination forKey: @"destination"];
    [opdict setObject: files forKey: @"files"];

    [fileOpsManager performOperation: opdict];

    *tag = 0;
    
    return YES;
  
  } else {
    NSRunAlertPanel(nil, 
                  NSLocalizedString(@"GWorkspace is logging out!", @""),
					        NSLocalizedString(@"Ok", @""), 
                  nil, 
                  nil);  
  }
  
  return NO;
}

- (BOOL)selectFile:(NSString *)fullPath
											inFileViewerRootedAtPath:(NSString *)rootFullpath
{
  FSNode *node = [FSNode nodeWithPath: fullPath];
  
  if (node && [node isValid]) {
    FSNode *base;
  
    if ((rootFullpath == nil) || ([rootFullpath length] == 0)) {
      base = [FSNode nodeWithPath: path_separator()];
    } else {
      base = [FSNode nodeWithPath: rootFullpath];
    }
  
    if (base && [base isValid]) {
      if (([base isDirectory] == NO) || [base isPackage]) {
        return NO;
      }
    
      [vwrsManager selectRepOfNode: node inViewerWithBaseNode: base];
      return YES;
    }
  }
   
  return NO;
}

- (int)extendPowerOffBy:(int)requested
{
  int req = (int)(requested / 1000);
  int ret;
  
  if (req > 0) {
    ret = (req < maxLogoutDelay) ? req : maxLogoutDelay;
  } else {
    ret = 0;
  }
  
  logoutDelay += ret;

  if (logoutTimer && [logoutTimer isValid]) {
    NSTimeInterval fireInterval = ([[logoutTimer fireDate] timeIntervalSinceNow] + ret);
    [logoutTimer setFireDate: [NSDate dateWithTimeIntervalSinceNow: fireInterval]];
  }
  
  return (ret * 1000);
}

- (NSArray *)launchedApplications
{
  NSMutableArray *launched = [NSMutableArray array];
  unsigned i;
  
  for (i = 0; i < [launchedApps count]; i++) {
    [launched addObject: [[launchedApps objectAtIndex: i] appInfo]];
  }

  return [launched makeImmutableCopyOnFail: NO];
}

- (NSDictionary *)activeApplication
{
  if (activeApplication != nil) {
    return [activeApplication appInfo];
  }
  return nil;
}

- (BOOL)openFile:(NSString *)fullPath
          withApplication:(NSString *)appname
            andDeactivate:(BOOL)flag
{
  NSString *appPath, *appName;
  GWLaunchedApp *app;
  id application;

  if (loggingout) {
    NSRunAlertPanel(nil, 
                  NSLocalizedString(@"GWorkspace is logging out!", @""),
					        NSLocalizedString(@"Ok", @""), 
                  nil, 
                  nil);  
    return NO;
  }
      
  if (appname == nil) {
    NSString *ext = [[fullPath pathExtension] lowercaseString];
    
    appname = [ws getBestAppInRole: nil forExtension: ext];
    
    if (appname == nil) {
      appname = defEditor;      
    }
  }

  [self applicationName: &appName andPath: &appPath forName: appname];
  
  app = [self launchedAppWithPath: appPath andName: appName];
  
  if (app == nil) {
    NSArray *args = [NSArray arrayWithObjects: @"-GSFilePath", fullPath, nil];
    
    return [self launchApplication: appname arguments: args];
  
  } else {  
    NSDate *delay = [NSDate dateWithTimeIntervalSinceNow: 0.1];
    
    /*
    * If we are opening many files together and our app is a wrapper,
    * we must wait a little for the last launched task to terminate.
    * Else we'd end waiting two seconds in -connectApplication.
    */
    [[NSRunLoop currentRunLoop] runUntilDate: delay];
    
    application = [app application];
    
    if (application == nil) {
      NSArray *args = [NSArray arrayWithObjects: @"-GSFilePath", fullPath, nil];
      
      [self applicationTerminated: app];
      
      return [self launchApplication: appname arguments: args];

    } else {
      NS_DURING
	      {
	    if (flag == NO) {
	      [application application: NSApp openFileWithoutUI: fullPath];
      } else {
	      [application application: NSApp openFile: fullPath];
	    }
	      }
      NS_HANDLER
	      {
      [self applicationTerminated: app]; 
	    NSWarnLog(@"Failed to contact '%@' to open file", appName);
	    return NO;
	      }
      NS_ENDHANDLER
    }
  }
  
  if (flag) {
    [NSApp deactivate];
  }

  return YES;
}

- (BOOL)launchApplication:(NSString *)appname
		             showIcon:(BOOL)showIcon
	             autolaunch:(BOOL)autolaunch
{
  NSString *appPath, *appName;
  GWLaunchedApp *app;
  id application;
  NSArray	*args = nil;

  if (loggingout) {
    NSRunAlertPanel(nil, 
                  NSLocalizedString(@"GWorkspace is logging out!", @""),
					        NSLocalizedString(@"Ok", @""), 
                  nil, 
                  nil);  
    return NO;
  }

  [self applicationName: &appName andPath: &appPath forName: appname];
 
  app = [self launchedAppWithPath: appPath andName: appName];
 
  if (app == nil) {
    if (autolaunch) {
	    args = [NSArray arrayWithObjects: @"-autolaunch", @"YES", nil];
	  }
    
    return [self launchApplication: appname arguments: args];
  
  } else {
    application = [app application];
 
    if (application == nil) {
      [self applicationTerminated: app];

      if (autolaunch) {
	      args = [NSArray arrayWithObjects: @"-autolaunch", @"YES", nil];
	    }
             
      return [self launchApplication: appname arguments: args];
    
    } else {
      [application activateIgnoringOtherApps: YES];
    }
  }

  return YES;
}

- (BOOL)openTempFile:(NSString *)fullPath
{
  NSString *ext = [[fullPath pathExtension] lowercaseString];
  NSString *name = [ws getBestAppInRole: nil forExtension: ext];
  NSString *appPath, *appName;
  GWLaunchedApp *app;
  id application;

  if (loggingout) {
    NSRunAlertPanel(nil, 
                  NSLocalizedString(@"GWorkspace is logging out!", @""),
					        NSLocalizedString(@"Ok", @""), 
                  nil, 
                  nil);  
    return NO;
  }
  
  if (name == nil) {
    NSWarnLog(@"No known applications for file extension '%@'", ext);
    return NO;
  }
  
  [self applicationName: &appName andPath: &appPath forName: name];  
    
  app = [self launchedAppWithPath: appPath andName: appName];
    
  if (app == nil) {
    NSArray *args = [NSArray arrayWithObjects: @"-GSTempPath", fullPath, nil];
    
    return [self launchApplication: name arguments: args];
  
  } else {
    application = [app application];
    
    if (application == nil) {
      NSArray *args = [NSArray arrayWithObjects: @"-GSTempPath", fullPath, nil];
    
      [self applicationTerminated: app];
      
      return [self launchApplication: name arguments: args];
      
    } else {
      NS_DURING
	      {
	    [application application: NSApp openTempFile: fullPath];
	      }
      NS_HANDLER
	      {
      [self applicationTerminated: app];
	    NSWarnLog(@"Failed to contact '%@' to open temp file", appName);
	    return NO;
	      }
      NS_ENDHANDLER
    }
  }    

  [NSApp deactivate];

  return YES;
}

@end


@implementation GWorkspace (Applications)

- (void)initializeWorkspace
{
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

  autoLogoutDelay = [defaults integerForKey: @"GSAutoLogoutDelay"];
  
  if (autoLogoutDelay == 0) {
    autoLogoutDelay = 120;
  }

  maxLogoutDelay = [defaults integerForKey: @"GSMaxLogoutDelay"];
  
  if (autoLogoutDelay == 0) {
    maxLogoutDelay = 30;
  }  

  wsnc = [ws notificationCenter];
  
  [wsnc addObserver: self
	         selector: @selector(appWillLaunch:)
		           name: NSWorkspaceWillLaunchApplicationNotification
		         object: nil];

  [wsnc addObserver: self
	         selector: @selector(appDidLaunch:)
		           name: NSWorkspaceDidLaunchApplicationNotification
		         object: nil];    

  [wsnc addObserver: self
	         selector: @selector(appDidTerminate:)
		           name: NSWorkspaceDidTerminateApplicationNotification
		         object: nil];    

  [wsnc addObserver: self
	         selector: @selector(appDidBecomeActive:)
		           name: NSApplicationDidBecomeActiveNotification
		         object: nil];

  [wsnc addObserver: self
	         selector: @selector(appDidResignActive:)
		           name: NSApplicationDidResignActiveNotification
		         object: nil];    

  [wsnc addObserver: self
	         selector: @selector(appDidHide:)
		           name: NSApplicationDidHideNotification
		         object: nil];

  [wsnc addObserver: self
	         selector: @selector(appDidUnhide:)
		           name: NSApplicationDidUnhideNotification
		         object: nil];    
    
  [self checkLastRunningApps];

  logoutTimer = nil;
  logoutDelay = 0;
  loggingout = NO;
}

- (void)applicationName:(NSString **)appName
                andPath:(NSString **)appPath
                forName:(NSString *)name
{
  *appName = [[name lastPathComponent] stringByDeletingPathExtension];
  *appPath = [ws fullPathForApplication: *appName];
}
                
- (BOOL)launchApplication:(NSString *)appname
		            arguments:(NSArray *)args
{
  NSString *appPath, *appName;
  NSTask *task;
  GWLaunchedApp *app;
  NSString *path;
  NSDictionary *userinfo;
  NSString *host;

  path = [ws locateApplicationBinary: appname];
  
  if (path == nil) {
	  return NO;
	}

  /*
  * Try to ensure that apps we launch display in this workspace
  * ie they have the same -NSHost specification.
  */
  host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"];
      
  if (host != nil) {
    NSHost *h = [NSHost hostWithName: host];
    
    if ([h isEqual: [NSHost currentHost]] == NO) {
      if ([args containsObject: @"-NSHost"] == NO) {
		    NSMutableArray *a;

		    if (args == nil) {
		      a = [NSMutableArray arrayWithCapacity: 2];
		    } else {
		      a = AUTORELEASE ([args mutableCopy]);
		    }
		    
        [a insertObject: @"-NSHost" atIndex: 0];
		    [a insertObject: host atIndex: 1];
		    args = a;
		  }
    }
	}

  [self applicationName: &appName andPath: &appPath forName: appname];
  
  if (appPath == nil) {
    [ws findApplications];
    [self applicationName: &appName andPath: &appPath forName: appname];
  }

  if (appPath == nil && [appname isAbsolutePath] == YES)
    {
      appPath = appname;
    }
  
  userinfo = [NSDictionary dictionaryWithObjectsAndKeys: appName, 
			                                                   @"NSApplicationName",
	                                                       appPath, 
                                                         @"NSApplicationPath",
	                                                       nil];
                 
  [wsnc postNotificationName: NSWorkspaceWillLaunchApplicationNotification
	                    object: ws
	                  userInfo: userinfo];

  task = [NSTask launchedTaskWithLaunchPath: path arguments: args];
  
  if (task == nil) {
	  return NO;
	}
  
  app = [GWLaunchedApp appWithApplicationPath: appPath
                              applicationName: appName
                                 launchedTask: task];
  
  if (app) {
    [launchedApps addObject: app];
    return YES;
  }
  
  return NO;    
}

- (void)appWillLaunch:(NSNotification *)notif
{
  NSDictionary *info = [notif userInfo];
  NSString *path = [info objectForKey: @"NSApplicationPath"];
  NSString *name = [info objectForKey: @"NSApplicationName"];
  
  if (path && name) {
    [[dtopManager dock] appWillLaunch: path appName: name];
    GWDebugLog(@"appWillLaunch: \"%@\" %@", name, path);
  } else {
    GWDebugLog(@"appWillLaunch: unknown application!");
  }
}

- (void)appDidLaunch:(NSNotification *)notif
{
  NSDictionary *info = [notif userInfo];
  NSString *name = [info objectForKey: @"NSApplicationName"];
  NSString *path = [info objectForKey: @"NSApplicationPath"];
  NSNumber *ident = [info objectForKey: @"NSApplicationProcessIdentifier"];
  GWLaunchedApp *app = [self launchedAppWithPath: path andName: name];

  if (app) {
    [app setIdentifier: ident];
    
  } else { 
    /*
    * if launched by an other process
    */
    app = [GWLaunchedApp appWithApplicationPath: path
                                applicationName: name
                              processIdentifier: ident
                                   checkRunning: NO];
    
    if (app && [app application]) {
      [launchedApps addObject: app];
    }  
  }

  if (app && [app application]) {
    [[dtopManager dock] appDidLaunch: path appName: name];
    GWDebugLog(@"\"%@\" appDidLaunch (%@)", name, path);
  }
}

- (void)appDidTerminate:(NSNotification *)notif
{
  /*
  * we do nothing here because we will know that the app has terminated 
  * from the connection.
  */
}

- (void)appDidBecomeActive:(NSNotification *)notif
{
  NSDictionary *info = [notif userInfo];
  NSString *name = [info objectForKey: @"NSApplicationName"];
  NSString *path = [info objectForKey: @"NSApplicationPath"];
  GWLaunchedApp *app = [self launchedAppWithPath: path andName: name];

  if (app) {
    unsigned i;
    
    for (i = 0; i < [launchedApps count]; i++) {
      GWLaunchedApp *a = [launchedApps objectAtIndex: i];
      [a setActive: (a == app)];
    }
    
    activeApplication = app;
    GWDebugLog(@"\"%@\" appDidBecomeActive", name);

  } else {
    activeApplication = nil;
    GWDebugLog(@"appDidBecomeActive: \"%@\" unknown running application.", name);
  }
}

- (void)appDidResignActive:(NSNotification *)notif
{
  NSDictionary *info = [notif userInfo];
  NSString *name = [info objectForKey: @"NSApplicationName"];
  NSString *path = [info objectForKey: @"NSApplicationPath"];
  GWLaunchedApp *app = [self launchedAppWithPath: path andName: name];
  
  if (app) {
    [app setActive: NO];
    
    if (app == activeApplication) {
      activeApplication = nil;
    }
    
  } else {
    GWDebugLog(@"appDidResignActive: \"%@\" unknown running application.", name);
  }
}

- (void)activateAppWithPath:(NSString *)path
                    andName:(NSString *)name
{
  GWLaunchedApp *app = [self launchedAppWithPath: path andName: name];

//  if (app && ([app isActive] == NO)) {
  if (app) {
    [app activateApplication];
  }
}

- (void)appDidHide:(NSNotification *)notif
{
  NSDictionary *info = [notif userInfo];
  NSString *name = [info objectForKey: @"NSApplicationName"];
  NSString *path = [info objectForKey: @"NSApplicationPath"];
  GWLaunchedApp *app = [self launchedAppWithPath: path andName: name];
  
  GWDebugLog(@"appDidHide: %@", name);
   
  if (app) {
    [app setHidden: YES];
    [[dtopManager dock] appDidHide: name];
  } else {
    GWDebugLog(@"appDidHide: \"%@\" unknown running application.", name);
  }
}

- (void)appDidUnhide:(NSNotification *)notif
{
  NSDictionary *info = [notif userInfo];
  NSString *name = [info objectForKey: @"NSApplicationName"];
  NSString *path = [info objectForKey: @"NSApplicationPath"];
  GWLaunchedApp *app = [self launchedAppWithPath: path andName: name];
    
  if (app) {
    [app setHidden: NO];
    [[dtopManager dock] appDidUnhide: name];
    GWDebugLog(@"\"%@\" appDidUnhide", name);
  } else {
    GWDebugLog(@"appDidUnhide: \"%@\" unknown running application.", name);
  }
}

- (void)unhideAppWithPath:(NSString *)path
                  andName:(NSString *)name
{
  GWLaunchedApp *app = [self launchedAppWithPath: path andName: name];

  if (app && [app isHidden]) {
    [app unhideApplication];
  }
}

- (void)applicationTerminated:(GWLaunchedApp *)app
{
  if (app == activeApplication) {
    activeApplication = nil;
  }
  
  [[dtopManager dock] appTerminated: [app name]];
  GWDebugLog(@"\"%@\" applicationTerminated", [app name]);  
  [launchedApps removeObject: app];  
  
  if (loggingout && ([launchedApps count] == 1)) {
    GWLaunchedApp *app = [launchedApps objectAtIndex: 0];

    if ([[app name] isEqual: gwProcessName]) {
      [NSApp terminate: self];
    }
  }
}

- (GWLaunchedApp *)launchedAppWithPath:(NSString *)path
                               andName:(NSString *)name
{
  if ((path != nil) && (name != nil)) {
    unsigned i;

    for (i = 0; i < [launchedApps count]; i++) {
      GWLaunchedApp *app = [launchedApps objectAtIndex: i];

      if (([[app path] isEqual: path]) && ([[app name] isEqual: name])) {
        return app;
      }
    }
  }
    
  return nil;
}

- (NSArray *)storedAppInfo
{
  NSDictionary *runningInfo = nil;
  NSDictionary *apps = nil;
  
  if ([storedAppinfoLock tryLock] == NO) {
    unsigned sleeps = 0;

    if ([[storedAppinfoLock lockDate] timeIntervalSinceNow] < -20.0) {
	    NS_DURING
	      {
	    [storedAppinfoLock breakLock];
	      }
	    NS_HANDLER
	      {
      NSLog(@"Unable to break lock %@ ... %@", storedAppinfoLock, localException);
	      }
	    NS_ENDHANDLER
    }
    
    for (sleeps = 0; sleeps < 10; sleeps++) {
	    if ([storedAppinfoLock tryLock] == YES) {
	      break;
	    }
	    
      sleeps++;
	    [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
	  }
    
    if (sleeps >= 10) {
      NSLog(@"Unable to obtain lock %@", storedAppinfoLock);
      return nil;
	  }
  }

  if ([fm isReadableFileAtPath: storedAppinfoPath]) {
    runningInfo = [NSDictionary dictionaryWithContentsOfFile: storedAppinfoPath];
  }
        
  [storedAppinfoLock unlock];
  
  if (runningInfo == nil) {
    return nil;
  }
  
  apps = [runningInfo objectForKey: @"GSLaunched"];
  
  if (apps != nil) {
    return [apps allValues];
  }
  
  return nil;
}

- (void)updateStoredAppInfoWithLaunchedApps:(NSArray *)apps
{
  CREATE_AUTORELEASE_POOL(arp);
  NSMutableDictionary *runningInfo = nil;
  NSDictionary *oldapps = nil;
  NSMutableDictionary *newapps = nil;
  BOOL modified = NO;
  unsigned i;
    
  if ([storedAppinfoLock tryLock] == NO) {
    unsigned sleeps = 0;

    if ([[storedAppinfoLock lockDate] timeIntervalSinceNow] < -20.0) {
	    NS_DURING
	      {
	    [storedAppinfoLock breakLock];
	      }
	    NS_HANDLER
	      {
      NSLog(@"Unable to break lock %@ ... %@", storedAppinfoLock, localException);
	      }
	    NS_ENDHANDLER
    }
    
    for (sleeps = 0; sleeps < 10; sleeps++) {
	    if ([storedAppinfoLock tryLock] == YES) {
	      break;
	    }
	    
      sleeps++;
	    [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
	  }
    
    if (sleeps >= 10) {
      NSLog(@"Unable to obtain lock %@", storedAppinfoLock);
      return;
	  }
  }

  if ([fm isReadableFileAtPath: storedAppinfoPath]) {
    runningInfo = [NSMutableDictionary dictionaryWithContentsOfFile: storedAppinfoPath];
  }

  if (runningInfo == nil) {
    runningInfo = [NSMutableDictionary dictionary];
    modified = YES;
  }

  oldapps = [runningInfo objectForKey: @"GSLaunched"];
  
  if (oldapps == nil) {
    newapps = [NSMutableDictionary new];
    modified = YES;
  } else {
    newapps = [oldapps mutableCopy];
  }
  
  for (i = 0; i < [apps count]; i++) {
    GWLaunchedApp *app = [apps objectAtIndex: i];
    NSString *appname = [app name];
    NSDictionary *oldInfo = [newapps objectForKey: appname];

    if ([app isRunning] == NO) {
      if (oldInfo != nil) {
        [newapps removeObjectForKey: appname];
	      modified = YES;
	    }

    } else {
      NSDictionary *info = [app appInfo];

      if ([info isEqual: oldInfo] == NO) {
        [newapps setObject: info forKey: appname];
	      modified = YES;
      }
    }
  }
  
  if (modified) {
    [runningInfo setObject: newapps forKey: @"GSLaunched"];
    [runningInfo writeToFile: storedAppinfoPath atomically: YES];
  }

  RELEASE (newapps);  
  [storedAppinfoLock unlock];
  RELEASE (arp);
}

- (void)checkLastRunningApps
{
  NSArray *oldrunning = [self storedAppInfo];

  if (oldrunning && [oldrunning count]) {
    NSMutableArray *toremove = [NSMutableArray array];
    unsigned i;
    
    for (i = 0; i < [oldrunning count]; i++) {
      NSDictionary *dict = [oldrunning objectAtIndex: i];
      NSString *name = [dict objectForKey: @"NSApplicationName"];
      NSString *path = [dict objectForKey: @"NSApplicationPath"];
      NSNumber *ident = [dict objectForKey: @"NSApplicationProcessIdentifier"];
    
      if (name && path && ident) {
        GWLaunchedApp *app = [GWLaunchedApp appWithApplicationPath: path
                                                   applicationName: name
                                                 processIdentifier: ident
                                                      checkRunning: YES];
        
        if ((app != nil) && [app isRunning]) {
          BOOL hidden = [app isApplicationHidden];
          
          [launchedApps addObject: app];
          [app setHidden: hidden];
          [[dtopManager dock] appDidLaunch: path appName: name];
          
          if (hidden) {
            [[dtopManager dock] appDidHide: name];
          }
          
        } else if (app != nil) {
          [toremove addObject: app];
        }
      }
    }
    
    if ([toremove count]) {
      [self updateStoredAppInfoWithLaunchedApps: toremove];
    }
  }
}

- (void)startLogout
{
  NSString *msg = [NSString stringWithFormat: @"%@\n%@%i %@",
        NSLocalizedString(@"Are you sure you want to quit\nall applications and log out now?", @""),
        NSLocalizedString(@"If you do nothing, the system will log out\nautomatically in", @""),
        autoLogoutDelay,
        NSLocalizedString(@"seconds.", @"")];
  
  loggingout = YES;
  logoutDelay = 30;
  
  if (logoutTimer && [logoutTimer isValid]) {
    [logoutTimer invalidate];
  }

  ASSIGN (logoutTimer, [NSTimer scheduledTimerWithTimeInterval: autoLogoutDelay
											                          target: self 
                                              selector: @selector(doLogout:) 
															                userInfo: nil 
                                               repeats: NO]);
                                        
  if (NSRunAlertPanel(NSLocalizedString(@"Logout", @""),
                      msg,
                      NSLocalizedString(@"Log out", @""),
                      NSLocalizedString(@"Cancel", @""),
                      nil)) {
    [logoutTimer invalidate]; 
    [self doLogout: nil];
  } else {
    [logoutTimer invalidate]; 
    DESTROY (logoutTimer);
    loggingout = NO;
  }
}

- (void)doLogout:(id)sender
{
  NSMutableArray *launched = [NSMutableArray array];
  GWLaunchedApp *gwapp = [self launchedAppWithPath: gwBundlePath andName: gwProcessName];
  unsigned i;

  [launched addObjectsFromArray: launchedApps];
  [launched removeObject: gwapp];
  
  for (i = 0; i < [launched count]; i++) {
    [[launched objectAtIndex: i] terminateApplication];      
  }

  [launched removeAllObjects];
  [launched addObjectsFromArray: launchedApps];
  [launched removeObject: gwapp];
    
  if ([launched count]) {
    ASSIGN (logoutTimer, [NSTimer scheduledTimerWithTimeInterval: logoutDelay
											                      target: self 
                                          selector: @selector(terminateTasks:) 
															            userInfo: nil 
                                           repeats: NO]);
  } else {
    [NSApp terminate: self];
  }
}

- (void)terminateTasks:(id)sender
{
  BOOL canterminate = YES;

  if ([launchedApps count] > 1) {
    NSMutableArray *launched = [NSMutableArray array];
    GWLaunchedApp *gwapp = [self launchedAppWithPath: gwBundlePath andName: gwProcessName];
    NSMutableString *appNames = [NSMutableString string];
    NSString *msg = nil;
    unsigned count;
    unsigned i;

    [launched addObjectsFromArray: launchedApps];
    [launched removeObject: gwapp];
    
    count = [launched count];
    
    for (i = 0; i < count; i++) {
      GWLaunchedApp *app = [launched objectAtIndex: i];
      
      [appNames appendString: [app name]];

      if (i < (count - 1)) {
        [appNames appendString: @", "];
      }
    }
    
    msg = [NSString stringWithFormat: @"%@\n%@\n%@",
                          NSLocalizedString(@"The following applications:", @""),
                          appNames, 
                          NSLocalizedString(@"refuse to terminate.", @"")];    

    if (NSRunAlertPanel(NSLocalizedString(@"Logout", @""),
                            msg,
                            NSLocalizedString(@"Kill applications", @""),
                            NSLocalizedString(@"Cancel logout", @""),
                            nil)) {
      for (i = 0; i < [launched count]; i++) {
        [[launched objectAtIndex: i] terminateTask];      
      }    
      
    } else {
      canterminate = NO;
    }
  }
  
  if (canterminate) {  
    [NSApp terminate: self];
  } else {
    loggingout = NO;
  }
}

@end


@implementation GWLaunchedApp

+ (id)appWithApplicationPath:(NSString *)apath
             applicationName:(NSString *)aname
                launchedTask:(NSTask *)atask
{
  GWLaunchedApp *app = [GWLaunchedApp new];
  
  [app setPath: apath];
  [app setName: aname];
  [app setTask: atask];
  
  if (([app name] == nil) || ([app path] == nil)) {
    DESTROY (app);
  }
  
  return TEST_AUTORELEASE (app);  
}

+ (id)appWithApplicationPath:(NSString *)apath
             applicationName:(NSString *)aname
           processIdentifier:(NSNumber *)ident
                checkRunning:(BOOL)check
{
  GWLaunchedApp *app = [GWLaunchedApp new];
  
  [app setPath: apath];
  [app setName: aname];
  [app setIdentifier: ident];
  
  if (([app name] == nil) || ([app path] == nil) || ([app identifier] == nil)) {
    DESTROY (app);
  } else if (check) {
    [app connectApplication: YES];
  }
  
  return TEST_AUTORELEASE (app);  
}

- (void)dealloc
{
  [nc removeObserver: self];

  if (conn && [conn isValid]) {
    DESTROY (application);  
    RELEASE (conn);  
  }
  
  RELEASE (name);
  RELEASE (path);
  TEST_RELEASE (identifier);
  TEST_RELEASE (task);
    
  [super dealloc];
}

- (id)init
{
  self = [super init];
  
  if (self) {
    task = nil;
    name = nil;
    path = nil; 
    identifier = nil;
    conn = nil;
    application = nil;
    active = NO;
    hidden = NO;
    
    gw = [GWorkspace gworkspace];
    nc = [NSNotificationCenter defaultCenter];      
  }
  
  return self;
}

- (unsigned)hash
{
  return ([name hash] | [path hash]);
}

- (BOOL)isEqual:(id)other
{
  if (other == self) {
    return YES;
  }
  
  if ([other isKindOfClass: [GWLaunchedApp class]]) {
    return ([[(GWLaunchedApp *)other name] isEqual: name]
                && [[(GWLaunchedApp *)other path] isEqual: path]);
  }
  
  return NO;
}

- (NSDictionary *)appInfo
{
  NSMutableDictionary *dict = [NSMutableDictionary dictionary];
  
  [dict setObject: name forKey: @"NSApplicationName"];
  [dict setObject: path forKey: @"NSApplicationPath"];
  
  if (identifier != nil) {
    [dict setObject: identifier forKey: @"NSApplicationProcessIdentifier"];
  }

  return [dict makeImmutableCopyOnFail: NO];
}

- (void)setTask:(NSTask *)atask
{
  ASSIGN (task, atask);
}

- (NSTask *)task
{
  return task;
}

- (void)setPath:(NSString *)apath
{
  ASSIGN (path, apath);
}

- (NSString *)path
{
  return path;
}

- (void)setName:(NSString *)aname
{
  ASSIGN (name, aname);
}

- (NSString *)name
{
  return name;
}

- (void)setIdentifier:(NSNumber *)ident
{
  ASSIGN (identifier, ident);
}

- (NSNumber *)identifier
{
  return identifier;
}

- (id)application
{
  [self connectApplication: NO];
  return application;
}

- (void)setActive:(BOOL)value
{
  active = value;
}

- (BOOL)isActive
{
  return active;
}

- (void)activateApplication
{
  NS_DURING
    {
  [application activateIgnoringOtherApps: YES];
    }
  NS_HANDLER
    {
  NSLog(@"Unable to activate %@", name);
  NSLog(@"GWorkspace caught exception %@: %@", 
	        [localException name], [localException reason]);
    }
  NS_ENDHANDLER
}    

- (void)setHidden:(BOOL)value
{
  hidden = value;
}

- (BOOL)isHidden
{
  return hidden;
}

- (void)hideApplication
{
  NS_DURING
    {
  [application hide: nil];
    }
  NS_HANDLER
    {
  NSLog(@"Unable to hide %@", name);
  NSLog(@"GWorkspace caught exception %@: %@", 
	        [localException name], [localException reason]);
    }
  NS_ENDHANDLER
}    

- (void)unhideApplication
{
  NS_DURING
    {
  [application unhideWithoutActivation];
    }
  NS_HANDLER
    {
  NSLog(@"Unable to unhide %@", name);
  NSLog(@"GWorkspace caught exception %@: %@", 
	        [localException name], [localException reason]);
    }
  NS_ENDHANDLER
}    

- (BOOL)isApplicationHidden
{
  BOOL apphidden = NO;
  
  if (application != nil) {
    NS_DURING
      {
    apphidden = [application isHidden];
      }
    NS_HANDLER
      {
    NSLog(@"GWorkspace caught exception %@: %@", 
	                      [localException name], [localException reason]);
      }
    NS_ENDHANDLER
  }
  
  return apphidden;
}

- (BOOL)gwlaunched
{
  return (task != nil);
}

- (BOOL)isRunning
{
  return (application != nil);
}

- (void)terminateApplication 
{  
  if (application) {
    NS_DURING
      {
    [application terminate: nil];
      }
    NS_HANDLER
      {
    GWDebugLog(@"GWorkspace caught exception %@: %@", 
	                      [localException name], [localException reason]);
      }
    NS_ENDHANDLER
  } else { 
    /* if the app is a wrapper */
    [gw applicationTerminated: self];
  }
}

- (void)terminateTask 
{
  if (task && [task isRunning]) {
    NS_DURING
      {
    [task terminate];      
      }
    NS_HANDLER
      {
    GWDebugLog(@"GWorkspace caught exception %@: %@", 
	                      [localException name], [localException reason]);
      }
    NS_ENDHANDLER
  }
}

- (void)connectApplication:(BOOL)showProgress
{
  if (application == nil) {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *host = [defaults stringForKey: @"NSHost"];
    id app = nil;
    
    if (host == nil) {
	    host = @"";
	  } else {
	    NSHost *h = [NSHost hostWithName: host];

      if ([h isEqual: [NSHost currentHost]]) {
	      host = @"";
	    }
	  }
  
    app = [NSConnection rootProxyForConnectionWithRegisteredName: name
                                                            host: host];

    if (app) {
      NSConnection *c = [app connectionForProxy];

	    [nc addObserver: self
	           selector: @selector(connectionDidDie:)
		             name: NSConnectionDidDieNotification
		           object: c];
      
      application = app;
      RETAIN (application);
      ASSIGN (conn, c);
      
	  } else {
      StartAppWin *startAppWin;
      int i;

	    if ((task == nil || [task isRunning] == NO) && (showProgress == NO)) {
        DESTROY (task);
        return;
	    }

      if (showProgress) {
        startAppWin = [gw startAppWin];
        [startAppWin showWindowWithTitle: @"GWorkspace"
                                 appName: name
                               operation: NSLocalizedString(@"contacting:", @"")         
                            maxProgValue: 20.0];
      }

      for (i = 0; i < 20; i++) {
        if (showProgress) {
          [startAppWin updateProgressBy: 1.0];
        }

	      [[NSRunLoop currentRunLoop] runUntilDate:
		                     [NSDate dateWithTimeIntervalSinceNow: 0.1]];

        app = [NSConnection rootProxyForConnectionWithRegisteredName: name
                                                                host: host];                  
        if (app) {
          NSConnection *c = [app connectionForProxy];

	        [nc addObserver: self
	               selector: @selector(connectionDidDie:)
		                 name: NSConnectionDidDieNotification
		               object: c];

          application = app;
          RETAIN (application);
          ASSIGN (conn, c);
          break;
        }
      }

      if (showProgress) {
        [[startAppWin win] close];
      }
      
      if (application == nil) {          
        if (task && [task isRunning]) {
          [task terminate];
        }
        DESTROY (task);
          
        if (showProgress == NO) {
          NSRunAlertPanel(NSLocalizedString(@"error", @""),
                      [NSString stringWithFormat: @"%@ %@", 
                          name, NSLocalizedString(@"seems to have hung", @"")], 
		                                      NSLocalizedString(@"OK", @""), 
                                          nil, 
                                          nil);
        }
      }
	  }
  }
}

- (void)connectionDidDie:(NSNotification *)notif
{
  if (conn == (NSConnection *)[notif object]) {
    [nc removeObserver: self
	                name: NSConnectionDidDieNotification
	              object: conn];

    DESTROY (application);
    DESTROY (conn);
    
    GWDebugLog(@"\"%@\" application connection did die", name);

    [gw applicationTerminated: self];
  }
}

@end


@implementation NSWorkspace (WorkspaceApplication)

- (id)_workspaceApplication
{
  return [GWorkspace gworkspace];
}

@end

