////////////////////////////////////////////////////////////////////////////
//
//  THREADSHELL.CPP
//
//  Run a console app as a detached, hidden process
//
//                                                  - Paul Roub  09/09/96
//
/////////////////////////////////////////////////////////////////////////////

#include  "stdafx.h"
#include  "io.h"
#include  "ThreadShell.h"


ThreadShell::ThreadShell()
{
     threadHand = processHand = INVALID_HANDLE_VALUE;
     m_consoleCreated = FALSE;
     m_dpaint=NULL;
}


ThreadShell::~ThreadShell()
{
     if (threadHand != INVALID_HANDLE_VALUE)
     {
          CloseHandle( threadHand );
     }
     if (processHand != INVALID_HANDLE_VALUE)
     {
          CloseHandle( processHand );
     }
     if (batName.GetLength() > 0)
     {
          remove( batName );
     }
     KillConsole();
     m_dpaint=NULL;
}

BOOL                               //   TRUE if app started OK
ThreadShell::StartApp(             // begin running a detached app
const CString &cmdStr,             //   command line for app (incl. params)
BOOL runMinimized,                 //   run minimized and hidden?
const char *title)                 //   window title (or NULL)
{
     DWORD     msToFind = 0xFFFFFFFF;
     DWORD     msToFind2 = 0xFFFFFFFF;
     BOOL      success = FALSE;
     static BOOL minimized = FALSE;

     error.Empty();

     ASSERT( batName.GetLength() <= 0 );

     if (batName.GetLength() > 0)
     {
          remove( batName );
          batName.Empty();
     }

     ASSERT( processHand == INVALID_HANDLE_VALUE );
     ASSERT( threadHand == INVALID_HANDLE_VALUE );

     if ((title != NULL) && (FindWindow( title, 0 ) == NULL))
     {
          m_title = title;
     }
     else
     {
          m_title = cmdStr + " " + GetTempName();
     }

     if (! m_consoleCreated)
     {
          m_consoleCreated = AllocConsole();
     }

     if (m_consoleCreated)
     {
          SetConsoleTitle( m_title );

          CWnd *wnd = FindWindow( m_title );

          if (wnd != NULL)
          {
               if (runMinimized)
               {
                    wnd->ShowWindow( SW_HIDE );
                    if (! minimized)
                    {
                         wnd->ShowWindow( SW_MINIMIZE );
                         minimized = TRUE;
                    }
                    wnd->ShowWindow( SW_HIDE );               
                    if (m_dpaint != NULL) {
                         m_dpaint->UpdateWindow();
                    }
               }

               if (CreateBatchFile( cmdStr, batName ))
               {
                    PROCESS_INFORMATION processInfo;
                    STARTUPINFO startInfo;

                    GetStartupInfo( &startInfo );

                    success = CreateProcess( NULL, (char *)(const char *)batName,
                                             NULL, NULL,
                                             FALSE, NORMAL_PRIORITY_CLASS,
                                             NULL,
                                             NULL,
                                             &startInfo, &processInfo
                                           );

                    if (success)
                    {
                         processHand = processInfo.hProcess;
                         threadHand = processInfo.hThread;
                    }
                    else
                    {
                         error = "Unable to run \"" + cmdStr + "\"";
                    }
               }
          }
          else
          {
               error = "Unable to locate console window";
               success = FALSE;
               FreeConsole();
          }

     }
     else
     {
          error = "Unable to allocate console";
          success = FALSE;
     }

     return( success );
}


BOOL                               //   TRUE if done or no app running
ThreadShell::Complete()            // check whether an app has completed
{
     if (processHand == INVALID_HANDLE_VALUE)
     {
          return( TRUE );
     }

     if (WaitForSingleObject( processHand, 0 ) != WAIT_OBJECT_0)
     {
          return( FALSE );
     }

     DWORD     exCode = 0;

     GetExitCodeProcess( processHand, &exCode );

     if (exCode == STILL_ACTIVE)
     {
          return( FALSE );
     }

     CloseHandle( processHand );
     CloseHandle( threadHand );

     processHand = threadHand = INVALID_HANDLE_VALUE;

     if (remove( batName ) != 0)
     {
          error = "Unable to remove " + batName;
     }
     else
     {
          batName.Empty();
     }

     return( TRUE );
}


CString                            //   description of last error detected
ThreadShell::Error()               // get description of the last error
{
     return( error );
}


BOOL                               //   TRUE if app canceled OK
ThreadShell::Cancel()              // cancel the currently shelled app
{
     if (Complete())
     {
          return( TRUE );
     }

     BOOL      success = TerminateProcess( processHand, 0xFFFFFFFF );

     if (success)
     {
          (void)Complete();
     }

     return( success );
}


void
ThreadShell::KillConsole()         // kill the console window - shouldn't have to call this
{
     if (! m_consoleCreated)
     {
          return;
     }

     CWnd      *wnd = FindWindow( m_title );

     if (wnd != NULL)
     {
          m_title = GetTempName();
          SetConsoleTitle( m_title );
          Sleep( 100 );
     }

     if (m_consoleCreated)
     {
          if (! FreeConsole())
          {
               error = "Unable to free console";
          }
          else
          {
               Sleep( 100 );

               DWORD     msToFind = 0;

               CWnd *wnd = FindWindow( m_title );

               if (wnd != NULL)
               {
                    wnd->ShowWindow( SW_MINIMIZE );
                    Sleep( 100 );

                    wnd = FindWindow( m_title, msToFind );

                    if (wnd != NULL)
                    {
                         wnd->UpdateWindow();
                         Sleep( 100 );
                    }

                    wnd = FindWindow( m_title, msToFind );

                    if (wnd != NULL)
                    {
                         wnd->SendMessage( WM_CLOSE );
                         Sleep( 100 );
                    }
               }
               else
               {
                    error = "Unable to locate console window";
               }

               wnd = FindWindow( m_title );
               ASSERT( wnd == NULL );
          }

          m_consoleCreated = FALSE;
     }
}


//   private functions
//

CWnd *                             //   pointer to found window
ThreadShell::FindWindow(           // find window with a given title
const char *st,                    //   window title
DWORD maxMs)                       //   ms to keep looking
{
     CWnd      *wnd = NULL;

     for ( int count = 0; count <= (int)(maxMs / 40); ++count )
     {
          wnd = CWnd::FindWindow( NULL, st );

          if (wnd != NULL)
          {
               break;
          }

          Sleep( 40 );

     }

     return( wnd );
}


BOOL                               //   TRUE if created OK
ThreadShell::CreateBatchFile(      // create batch for a given command
const CString &cmdLine,            //   command line we're running
CString &batName)                  //   resulting batch file name
{
     batName = GetTempName() + ".bat";

     FILE     *outf = fopen( batName, "w" );

     if (outf == NULL)
     {
          error = "Couldn't create batch file " + batName;
          return( FALSE );
     }

     fprintf( outf, "@echo off\n"
                    "%s\n"
                    "@cls",             // so it can be closed (Win95)
                    (const char *)cmdLine 
            );

     fclose( outf );

     return( TRUE );
}


CString                            //   unique filename prefix
ThreadShell::GetTempName()         // get a temporary file name
{
     CString   result;

     char      tname[ 9 ];
     strcpy( tname, "gtXXXXXX" );

     const char *mkres = _mktemp( tname );

     if (mkres != NULL)
     {
          result = mkres;
     }

     return( result );
}


void
ThreadShell::WaitForCompletion()   // block until the current app completes
{
     if (Complete())
     {
          return;
     }

     ASSERT( processHand != NULL );

     if (AfxGetApp() == NULL)
     {
          WaitForSingleObject( processHand, INFINITE );
     }
     else
     {
          CWaitCursor cursor;

          WaitForSingleObject( processHand, INFINITE );
     }
     (void)Complete();
}

VOID ThreadShell::SetUpdateWindow(CDialog *pdlg)
{
     m_dpaint=pdlg;
}
