/* This file is part of the KDE libraries
    Copyright (C) 2000 Torben Weis <weis@kde.org>
    Copyright (C) 2006 David Faure <faure@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "krun.h"

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <typeinfo>

#include <tqwidget.h>
#include <tqguardedptr.h>

#include "kuserprofile.h"
#include "kmimetype.h"
#include "kmimemagic.h"
#include "tdeio/job.h"
#include "tdeio/global.h"
#include "tdeio/scheduler.h"
#include "tdeio/netaccess.h"
#include "tdefile/kopenwith.h"
#include "tdefile/tderecentdocument.h"

#include <kdatastream.h>
#include <kmessageboxwrapper.h>
#include <kurl.h>
#include <tdeapplication.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <kprotocolinfo.h>
#include <kstandarddirs.h>
#include <kprocess.h>
#include <dcopclient.h>
#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqtextstream.h>
#include <tqdatetime.h>
#include <tqregexp.h>
#include <kdesktopfile.h>
#include <tdestartupinfo.h>
#include <kmacroexpander.h>
#include <kshell.h>
#include <kde_file.h>
#include <kstringhandler.h>

#ifdef Q_WS_X11
#include <twin.h>
#endif

class KRun::KRunPrivate
{
public:
    KRunPrivate() { m_showingError = false; }

    bool m_showingError;
    bool m_runExecutables;

    TQString m_preferredService;
    TQString m_externalBrowser;
    TQString m_localPath;
    TQString m_suggestedFileName;
    TQGuardedPtr <TQWidget> m_window;
    TQCString m_asn;
};

pid_t KRun::runURL( const KURL& u, const TQString& _mimetype )
{
    return runURL( u, _mimetype, false, true, TQString::null );
}

pid_t KRun::runURL( const KURL& u, const TQString& _mimetype, bool tempFile )
{
    return runURL( u, _mimetype, tempFile, true, TQString::null );
}

pid_t KRun::runURL( const KURL& u, const TQString& _mimetype, bool tempFile, bool runExecutables )
{
    return runURL( u, _mimetype, tempFile, runExecutables, TQString::null );
}

bool KRun::isExecutableFile( const KURL& url, const TQString &mimetype )
{
  if ( !url.isLocalFile() )
     return false;
  TQFileInfo file( url.path() );
  if ( file.isExecutable() )  // Got a prospective file to run
  {
    KMimeType::Ptr mimeType = KMimeType::mimeType( mimetype );

    if ( mimeType->is("application/x-executable") || mimeType->is("application/x-executable-script") )
      return true;
  }
  return false;
}

pid_t KRun::runURL( const KURL& u, const TQString& _mimetype, bool tempFile, bool runExecutables, const TQString& suggestedFileName )
{
    return runURL( u, _mimetype, NULL, "", tempFile, runExecutables, suggestedFileName );
}

// This is called by foundMimeType, since it knows the mimetype of the URL
pid_t KRun::runURL( const KURL& u, const TQString& _mimetype, TQWidget* window, const TQCString& asn,
    bool tempFile, bool runExecutables, const TQString& suggestedFileName )
{
  bool noRun = false;
  bool noAuth = false;
  if ( _mimetype == "inode/directory-locked" )
  {
    KMessageBoxWrapper::error( window,
            i18n("<qt>Unable to enter <b>%1</b>.\nYou do not have access rights to this location.</qt>").arg(u.htmlURL()) );
    return 0;
  }
  else if ( (_mimetype == "application/x-desktop") ||
            (_mimetype == "media/builtin-mydocuments") ||
            (_mimetype == "media/builtin-mycomputer") ||
            (_mimetype == "media/builtin-mynetworkplaces") ||
            (_mimetype == "media/builtin-printers") ||
            (_mimetype == "media/builtin-trash") ||
            (_mimetype == "media/builtin-webbrowser") )
  {
    if ( u.isLocalFile() && runExecutables )
      return KDEDesktopMimeType::run( u, true );
  }
  else if ( isExecutableFile(u, _mimetype) )
  {
    if ( u.isLocalFile() && runExecutables)
    {
      if (kapp->authorize("shell_access"))
      {
        TQString path = u.path();
        shellQuote( path );
        return (KRun::runCommand(path, TQString::null, TQString::null, window, asn)); // just execute the url as a command
        // ## TODO implement deleting the file if tempFile==true
      }
      else
      {
        noAuth = true;
      }
    }
    else if (_mimetype == "application/x-executable")
      noRun = true;
  }
  else if ( isExecutable(_mimetype) )
  {
    if (!runExecutables)
      noRun = true;

    if (!kapp->authorize("shell_access"))
      noAuth = true;
  }

  if ( noRun )
  {
    KMessageBox::sorry( window,
        i18n("<qt>The file <b>%1</b> is an executable program. "
             "For safety it will not be started.</qt>").arg(u.htmlURL()));
    return 0;
  }
  if ( noAuth )
  {
    KMessageBoxWrapper::error( window,
        i18n("<qt>You do not have permission to run <b>%1</b>.</qt>").arg(u.htmlURL()) );
    return 0;
  }

  KURL::List lst;
  lst.append( u );

  static const TQString& app_str = TDEGlobal::staticQString("Application");

  KService::Ptr offer = KServiceTypeProfile::preferredService( _mimetype, app_str );

  if ( !offer )
  {
    // Open-with dialog
    // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog !
    // Hmm, in fact KOpenWithDlg::setServiceType already guesses the mimetype from the first URL of the list...
    return displayOpenWithDialog( lst, tempFile, suggestedFileName );
  }

  return KRun::run( *offer, lst, window, asn, tempFile, suggestedFileName );
}

bool KRun::displayOpenWithDialog( const KURL::List& lst )
{
    return displayOpenWithDialog( lst, false, TQString::null );
}

bool KRun::displayOpenWithDialog( const KURL::List& lst, bool tempFiles )
{
    return displayOpenWithDialog( lst, tempFiles, TQString::null );
}

bool KRun::displayOpenWithDialog( const KURL::List& lst, bool tempFiles, const TQString& suggestedFileName )
{
    if (kapp && !kapp->authorizeTDEAction("openwith"))
    {
       // TODO: Better message, i18n freeze :-(
       KMessageBox::sorry(0L, i18n("You are not authorized to open this file."));
       return false;
    }

    KOpenWithDlg l( lst, i18n("Open with:"), TQString::null, 0L );
    if ( l.exec() )
    {
      KService::Ptr service = l.service();
      if ( !!service )
        return KRun::run( *service, lst, 0 /*window*/, tempFiles, suggestedFileName );

      kdDebug(7010) << "No service set, running " << l.text() << endl;
      return KRun::run( l.text(), lst, suggestedFileName ); // TODO handle tempFiles
    }
    return false;
}

void KRun::shellQuote( TQString &_str )
{
    // Credits to Walter, says Bernd G. :)
    if (_str.isEmpty()) // Don't create an explicit empty parameter
        return;
    TQChar q('\'');
    _str.replace(q, "'\\''").prepend(q).append(q);
}


class KRunMX1 : public KMacroExpanderBase {
public:
    KRunMX1( const KService &_service ) :
        KMacroExpanderBase( '%' ), hasUrls( false ), hasSpec( false ), service( _service ) {}
    bool hasUrls:1, hasSpec:1;

protected:
    virtual int expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret );

private:
    const KService &service;
};

int
KRunMX1::expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret )
{
   uint option = str[pos + 1];
   switch( option ) {
   case 'c':
      ret << service.name().replace( '%', "%%" );
      break;
   case 'k':
      ret << service.desktopEntryPath().replace( '%', "%%" );
      break;
   case 'i':
      ret << "-icon" << service.icon().replace( '%', "%%" );
      break;
   case 'm':
      ret << "-miniicon" << service.icon().replace( '%', "%%" );
      break;
   case 'u':
   case 'U':
      hasUrls = true;
      /* fallthrough */
   case 'f':
   case 'F':
   case 'n':
   case 'N':
   case 'd':
   case 'D':
   case 'v':
      hasSpec = true;
      /* fallthrough */
   default:
      return -2; // subst with same and skip
   }
   return 2;
}

class KRunMX2 : public KMacroExpanderBase {
public:
    KRunMX2( const KURL::List &_urls ) :
        KMacroExpanderBase( '%' ), ignFile( false ), urls( _urls ) {}
    bool ignFile:1;

protected:
    virtual int expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret );

private:
    void subst( int option, const KURL &url, TQStringList &ret );

    const KURL::List &urls;
};

void
KRunMX2::subst( int option, const KURL &url, TQStringList &ret )
{
   switch( option ) {
   case 'u':
      ret << url.pathOrURL();
      break;
   case 'd':
      ret << url.directory();
      break;
   case 'f':
      ret << url.path();
      break;
   case 'n':
      ret << url.fileName();
      break;
   case 'v':
      if (url.isLocalFile() && TQFile::exists( url.path() ) )
          ret << KDesktopFile( url.path(), true ).readEntry( "Dev" );
      break;
   }
   return;
}

int
KRunMX2::expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret )
{
   uint option = str[pos + 1];
   switch( option ) {
   case 'f':
   case 'u':
   case 'n':
   case 'd':
   case 'v':
      if( urls.isEmpty() ) {
         if (!ignFile)
            kdDebug() << "KRun::processDesktopExec: No URLs supplied to single-URL service " << str << endl;
      } else if( urls.count() > 1 )
          kdWarning() << "KRun::processDesktopExec: " << urls.count() << " URLs supplied to single-URL service " << str << endl;
      else
         subst( option, urls.first(), ret );
      break;
   case 'F':
   case 'U':
   case 'N':
   case 'D':
      option += 'a' - 'A';
      for( KURL::List::ConstIterator it = urls.begin(); it != urls.end(); ++it )
         subst( option, *it, ret );
      break;
   case '%':
      ret = "%";
      break;
   default:
      return -2; // subst with same and skip
   }
   return 2;
}

// BIC: merge methods below
TQStringList KRun::processDesktopExec(const KService &_service, const KURL::List& _urls, bool has_shell) {
    return processDesktopExec( _service, _urls, has_shell, false, TQString::null );
}

TQStringList KRun::processDesktopExec(const KService &_service, const KURL::List& _urls, bool has_shell /* KDE4: remove */, bool tempFiles)
{
    return processDesktopExec( _service, _urls, has_shell, tempFiles, TQString::null );
}

TQStringList KRun::processDesktopExec(const KService &_service, const KURL::List& _urls, bool has_shell /* KDE4: remove */, bool tempFiles, const TQString& suggestedFileName)
{
  TQString exec = _service.exec();
  TQStringList result;
  bool appHasTempFileOption;

  KRunMX1 mx1( _service );
  KRunMX2 mx2( _urls );

  /// compatibility hack -- KDE 4: remove
  TQRegExp re("^\\s*(?:/bin/)?sh\\s+-c\\s+(.*)$");
  if (!re.search( exec )) {
    exec = TQString(re.cap( 1 )).stripWhiteSpace();
    for (uint pos = 0; pos < exec.length(); ) {
      TQChar c = exec.unicode()[pos];
      if (c != '\'' && c != '"')
        goto synerr; // what else can we do? after normal parsing the substs would be insecure
      int pos2 = exec.find( c, pos + 1 ) - 1;
      if (pos2 < 0)
        goto synerr; // quoting error
      memcpy( (void *)(exec.unicode() + pos), exec.unicode() + pos + 1, (pos2 - pos) * sizeof(TQChar));
      pos = pos2;
      exec.remove( pos, 2 );
    }
  }

  if( !mx1.expandMacrosShellQuote( exec ) )
    goto synerr; // error in shell syntax

  // FIXME: the current way of invoking tdeioexec disables term and su use

  // Check if we need "tempexec" (tdeioexec in fact)
  appHasTempFileOption = tempFiles && _service.property("X-TDE-HasTempFileOption").toBool();
  if( tempFiles && !appHasTempFileOption && _urls.size() ) {
    result << "tdeioexec" << "--tempfiles" << exec;
    result += _urls.toStringList();
    if (has_shell)
      result = KShell::joinArgs( result );
    return result;
  }

  // Check if we need tdeioexec
  if( !mx1.hasUrls ) {
    for( KURL::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it )
      if ( !(*it).isLocalFile() && !KProtocolInfo::isHelperProtocol(*it) ) {
        // We need to run the app through tdeioexec
        result << "tdeioexec";
        if ( tempFiles )
            result << "--tempfiles";
        if ( !suggestedFileName.isEmpty() ) {
            result << "--suggestedfilename";
            result << suggestedFileName;
        }
        result << exec;
        result += _urls.toStringList();
        if (has_shell)
          result = KShell::joinArgs( result );
        return result;
      }
  }

  if ( appHasTempFileOption )
      exec += " --tempfile";

  // Did the user forget to append something like '%f'?
  // If so, then assume that '%f' is the right choice => the application
  // accepts only local files.
  if( !mx1.hasSpec ) {
    exec += " %f";
    mx2.ignFile = true;
  }

  mx2.expandMacrosShellQuote( exec ); // syntax was already checked, so don't check return value

/*
 1 = need_shell, 2 = terminal, 4 = su, 8 = has_shell

 0                                                           << split(cmd)
 1                                                           << "sh" << "-c" << cmd
 2 << split(term) << "-e"                                    << split(cmd)
 3 << split(term) << "-e"                                    << "sh" << "-c" << cmd

 4                        << "tdesu" << "-u" << user << "-c" << cmd
 5                        << "tdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd))
 6 << split(term) << "-e" << "su"            << user << "-c" << cmd
 7 << split(term) << "-e" << "su"            << user << "-c" << ("sh -c " + quote(cmd))

 8                                                           << cmd
 9                                                           << cmd
 a << term        << "-e"                                    << cmd
 b << term        << "-e"                                    << ("sh -c " + quote(cmd))

 c                        << "tdesu" << "-u" << user << "-c" << quote(cmd)
 d                        << "tdesu" << "-u" << user << "-c" << quote("sh -c " + quote(cmd))
 e << term        << "-e" << "su"            << user << "-c" << quote(cmd)
 f << term        << "-e" << "su"            << user << "-c" << quote("sh -c " + quote(cmd))

 "sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh.
 this could be optimized with the -s switch of some su versions (e.g., debian linux).
*/

  if (_service.terminal()) {
    TDEConfigGroupSaver gs(TDEGlobal::config(), "General");
    TQString terminal = TDEGlobal::config()->readPathEntry("TerminalApplication", "konsole");
    if (terminal == "konsole")
      terminal += " -caption=%c %i %m";
    terminal += " ";
    terminal += _service.terminalOptions();
    if( !mx1.expandMacrosShellQuote( terminal ) ) {
      kdWarning() << "KRun: syntax error in command `" << terminal << "', service `" << _service.name() << "'" << endl;
      return TQStringList();
    }
    mx2.expandMacrosShellQuote( terminal );
    if (has_shell)
      result << terminal;
    else
      result = KShell::splitArgs( terminal ); // assuming that the term spec never needs a shell!
    result << "-e";
  }

  int err;
  if (_service.substituteUid()) {
    if (_service.terminal())
      result << "su";
    else
      result << "tdesu" << "-u";
    result << _service.username() << "-c";
    KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
    if (err == KShell::FoundMeta) {
      shellQuote( exec );
      exec.prepend( "/bin/sh -c " );
    } else if (err != KShell::NoError)
      goto synerr;
    if (has_shell)
      shellQuote( exec );
    result << exec;
  } else {
    if (has_shell) {
      if (_service.terminal()) {
        KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
        if (err == KShell::FoundMeta) {
          shellQuote( exec );
          exec.prepend( "/bin/sh -c " );
        } else if (err != KShell::NoError)
          goto synerr;
      }
      result << exec;
    } else {
      result += KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
      if (err == KShell::FoundMeta)
        result << "/bin/sh" << "-c" << exec;
      else if (err != KShell::NoError)
        goto synerr;
    }
  }

  return result;

 synerr:
  kdWarning() << "KRun: syntax error in command `" << _service.exec() << "', service `" << _service.name() << "'" << endl;
  return TQStringList();
}

//static
TQString KRun::binaryName( const TQString & execLine, bool removePath )
{
  // Remove parameters and/or trailing spaces.
  TQStringList args = KShell::splitArgs( execLine );
  for (TQStringList::ConstIterator it = args.begin(); it != args.end(); ++it)
    if (!(*it).contains('='))
      // Remove path if wanted
  return removePath ? (*it).mid(TQString(*it).findRev('/') + 1) : *it; 
  return TQString();
}

static pid_t runCommandInternal( TDEProcess* proc, const KService* service, const TQString& binName,
    const TQString &execName, const TQString & iconName, TQWidget* window, TQCString asn )
{
  if (service && !service->desktopEntryPath().isEmpty()
      && !KDesktopFile::isAuthorizedDesktopFile( service->desktopEntryPath() ))
  {
     kdWarning() << "No authorization to execute " << service->desktopEntryPath() << endl;
     KMessageBox::sorry(window, i18n("You are not authorized to execute this file."));
     return 0;
  }
  TQString bin = KRun::binaryName( binName, true );
#ifdef Q_WS_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification
  bool silent;
  TQCString wmclass;
  TDEStartupInfoId id;
  bool startup_notify = ( asn != "0" && KRun::checkStartupNotify( binName, service, &silent, &wmclass ));
  if( startup_notify )
  {
      id.initId( asn );
      id.setupStartupEnv();
      TDEStartupInfoData data;
      data.setHostname();
      data.setBin( bin );
      if( !execName.isEmpty())
          data.setName( execName );
      else if( service && !service->name().isEmpty())
          data.setName( service->name());
      data.setDescription( i18n( "Launching %1" ).arg( data.name()));
      if( !iconName.isEmpty())
          data.setIcon( iconName );
      else if( service && !service->icon().isEmpty())
          data.setIcon( service->icon());
      if( !wmclass.isEmpty())
          data.setWMClass( wmclass );
      if( silent )
          data.setSilent( TDEStartupInfoData::Yes );
      data.setDesktop( KWin::currentDesktop());
      if( window )
          data.setLaunchedBy( window->winId());
      TDEStartupInfo::sendStartup( id, data );
  }
  pid_t pid = TDEProcessRunner::run( proc, binName, id );
  if( startup_notify && pid )
  {
      TDEStartupInfoData data;
      data.addPid( pid );
      TDEStartupInfo::sendChange( id, data );
      TDEStartupInfo::resetStartupEnv();
  }
  return pid;
#else
  Q_UNUSED( execName );
  Q_UNUSED( iconName );
  return TDEProcessRunner::run( proc, bin );
#endif
}

// This code is also used in tdelauncher.
bool KRun::checkStartupNotify( const TQString& /*binName*/, const KService* service, bool* silent_arg, TQCString* wmclass_arg )
{
  bool silent = false;
  TQCString wmclass;
  if( service && service->property( "StartupNotify" ).isValid())
  {
      silent = !service->property( "StartupNotify" ).toBool();
      wmclass = service->property( "StartupWMClass" ).toString().latin1();
  }
  else if( service && service->property( "X-TDE-StartupNotify" ).isValid())
  {
      silent = !service->property( "X-TDE-StartupNotify" ).toBool();
      wmclass = service->property( "X-TDE-WMClass" ).toString().latin1();
  }
  else // non-compliant app
  {
      if( service )
      {
          if( service->type() == "Application" )
              wmclass = "0"; // doesn't have .desktop entries needed, start as non-compliant
          else
              return false; // no startup notification at all
      }
      else
      {
#if 0
        // Create startup notification even for apps for which there shouldn't be any,
        // just without any visual feedback. This will ensure they'll be positioned on the proper
        // virtual desktop, and will get user timestamp from the ASN ID.
          wmclass = "0";
          silent = true;
#else   // That unfortunately doesn't work, when the launched non-compliant application
        // launches another one that is compliant and there is any delay inbetween (bnc:#343359)
          return false;
#endif
      }
  }
  if( silent_arg != NULL )
      *silent_arg = silent;
  if( wmclass_arg != NULL )
      *wmclass_arg = wmclass;
  return true;
}

static pid_t runTempService( const KService& _service, const KURL::List& _urls, TQWidget* window,
    const TQCString& asn, bool tempFiles, const TQString& suggestedFileName )
{
  if (!_urls.isEmpty()) {
    kdDebug(7010) << "runTempService: first url " << _urls.first().url() << endl;
  }

  TQStringList args;
  if ((_urls.count() > 1) && !_service.allowMultipleFiles())
  {
      // We need to launch the application N times. That sucks.
      // We ignore the result for application 2 to N.
      // For the first file we launch the application in the
      // usual way. The reported result is based on this
      // application.
      KURL::List::ConstIterator it = _urls.begin();
      while(++it != _urls.end())
      {
         KURL::List singleUrl;
         singleUrl.append(*it);
         runTempService( _service, singleUrl, window, "", tempFiles, suggestedFileName );
      }
      KURL::List singleUrl;
      singleUrl.append(_urls.first());
      args = KRun::processDesktopExec(_service, singleUrl, false, tempFiles, suggestedFileName);
  }
  else
  {
      args = KRun::processDesktopExec(_service, _urls, false, tempFiles, suggestedFileName);
  }
  kdDebug(7010) << "runTempService: TDEProcess args=" << args << endl;

  TDEProcess * proc = new TDEProcess;
  *proc << args;

  if (!_service.path().isEmpty())
     proc->setWorkingDirectory(_service.path());

  return runCommandInternal( proc, &_service, KRun::binaryName( _service.exec(), false ),
                             _service.name(), _service.icon(), window, asn );
}

// WARNING: don't call this from processDesktopExec, since tdelauncher uses that too...
static KURL::List resolveURLs( const KURL::List& _urls, const KService& _service )
{
  // Check which protocols the application supports.
  // This can be a list of actual protocol names, or just TDEIO for KDE apps.
  TQStringList supportedProtocols = _service.property("X-TDE-Protocols").toStringList();
  KRunMX1 mx1( _service );
  TQString exec = _service.exec();
  if ( mx1.expandMacrosShellQuote( exec ) && !mx1.hasUrls ) {
    Q_ASSERT( supportedProtocols.isEmpty() ); // huh? If you support protocols you need %u or %U...
  } else {
    if ( supportedProtocols.isEmpty() )
    {
      // compat mode: assume TDEIO if not set and it's a KDE app
      TQStringList categories = _service.property("Categories").toStringList();
      if (( categories.find("TDE") != categories.end() ) && ( categories.find("KDE") != categories.end() ))
         supportedProtocols.append( "TDEIO" );
      else { // if no KDE app, be a bit over-generic
         supportedProtocols.append( "http");
         supportedProtocols.append( "ftp");
      }
    }
  }
  kdDebug(7010) << "supportedProtocols:" << supportedProtocols << endl;

  KURL::List urls( _urls );
  if ( supportedProtocols.find( "TDEIO" ) == supportedProtocols.end() ) {
    for( KURL::List::Iterator it = urls.begin(); it != urls.end(); ++it ) {
      const KURL url = *it;
      bool supported = url.isLocalFile() || supportedProtocols.find( url.protocol().lower() ) != supportedProtocols.end();
      kdDebug(7010) << "Looking at url=" << url << " supported=" << supported << endl;
      if ( !supported && KProtocolInfo::protocolClass(url.protocol()) == ":local" )
      {
        // Maybe we can resolve to a local URL?
        KURL localURL = TDEIO::NetAccess::mostLocalURL( url, 0 );
        if ( localURL != url ) {
          *it = localURL;
          kdDebug(7010) << "Changed to " << localURL << endl;
        }
      }
    }
  }
  return urls;
}

// BIC merge methods below
pid_t KRun::run( const KService& _service, const KURL::List& _urls )
{
    return run( _service, _urls, 0, false, TQString::null );
}

pid_t KRun::run( const KService& _service, const KURL::List& _urls, bool tempFiles )
{
    return run( _service, _urls, 0, tempFiles, TQString::null );
}

pid_t KRun::run( const KService& _service, const KURL::List& _urls, TQWidget* window, bool tempFiles )
{
    return run( _service, _urls, window, "", tempFiles, TQString::null );
}

pid_t KRun::run( const KService& _service, const KURL::List& _urls, TQWidget* window, const TQCString& asn, bool tempFiles )
{
    return run( _service, _urls, window, asn, tempFiles, TQString::null );
}

pid_t KRun::run( const KService& _service, const KURL::List& _urls, TQWidget* window, bool tempFiles, const TQString& suggestedFileName )
{
    return run( _service, _urls, window, "", tempFiles, suggestedFileName );
}

pid_t KRun::run( const KService& _service, const KURL::List& _urls, TQWidget* window, const TQCString& asn,
    bool tempFiles, const TQString& suggestedFileName )
{
  if (!_service.desktopEntryPath().isEmpty() &&
      !KDesktopFile::isAuthorizedDesktopFile( _service.desktopEntryPath()))
  {
     kdWarning() << "No authorization to execute " << _service.desktopEntryPath() << endl;
     KMessageBox::sorry(window, i18n("You are not authorized to execute this service."));
     return 0;
  }

  if ( !tempFiles )
  {
      // Remember we opened those urls, for the "recent documents" menu in kicker
      KURL::List::ConstIterator it = _urls.begin();
      for(; it != _urls.end(); ++it) {
          //kdDebug(7010) << "TDERecentDocument::adding " << (*it).url() << endl;
          TDERecentDocument::add( *it, _service.desktopEntryName() );
      }
  }

  if ( tempFiles || _service.desktopEntryPath().isEmpty() || !suggestedFileName.isEmpty() )
  {
     return runTempService(_service, _urls, window, asn, tempFiles, suggestedFileName);
  }

  kdDebug(7010) << "KRun::run " << _service.desktopEntryPath() << endl;

  if (!_urls.isEmpty()) {
    kdDebug(7010) << "First url " << _urls.first().url() << endl;
  }

  // Resolve urls if needed, depending on what the app supports
  const KURL::List urls = resolveURLs( _urls, _service );

  TQString error;
  int pid = 0;

  TQCString myasn = asn;
  // startServiceByDesktopPath() doesn't take TQWidget*, add it to the startup info now
  if( window != NULL )
  {
    if( myasn.isEmpty())
        myasn = TDEStartupInfo::createNewStartupId();
    if( myasn != "0" )
    {
        TDEStartupInfoId id;
        id.initId( myasn );
        TDEStartupInfoData data;
        data.setLaunchedBy( window->winId());
        TDEStartupInfo::sendChange( id, data );
    }
  }

  int i = TDEApplication::startServiceByDesktopPath(
        _service.desktopEntryPath(), urls.toStringList(), &error, 0L, &pid, myasn
        );

  if (i != 0)
  {
     kdDebug(7010) << error << endl;
     KMessageBox::sorry( window, error );
     return 0;
  }

  kdDebug(7010) << "startServiceByDesktopPath worked fine" << endl;
  return (pid_t) pid;
}


pid_t KRun::run( const TQString& _exec, const KURL::List& _urls, const TQString& _name,
                const TQString& _icon, const TQString&, const TQString&)
{
  KService::Ptr service = new KService(_name, _exec, _icon);

  return run(*service, _urls);
}

pid_t KRun::runCommand( TQString cmd )
{
  return KRun::runCommand( cmd, TQString::null, TQString::null, NULL, "" );
}

pid_t KRun::runCommand( const TQString& cmd, const TQString &execName, const TQString & iconName )
{
  return KRun::runCommand( cmd, execName, iconName, NULL, "" );
}

pid_t KRun::runCommand( const TQString& cmd, const TQString &execName, const TQString & iconName,
    TQWidget* window, const TQCString& asn )
{
  kdDebug(7010) << "runCommand " << cmd << "," << execName << endl;
  TDEProcess * proc = new TDEProcess;
  proc->setUseShell(true);
  *proc << cmd;
  KService::Ptr service = KService::serviceByDesktopName( binaryName( execName, true ) );
  TQString bin = binaryName( cmd, false );
  int pos = bin.findRev( '/' );
  if (pos != -1) {
    proc->setWorkingDirectory( bin.mid(0, pos) );
  }
  return runCommandInternal( proc, service.data(), binaryName( execName, false ), execName, iconName, window, asn );
}

KRun::KRun( const KURL& url, mode_t mode, bool isLocalFile, bool showProgressInfo )
     :m_timer(0,"KRun::timer")
{
  init (url, 0, "", mode, isLocalFile, showProgressInfo);
}

KRun::KRun( const KURL& url, TQWidget* window, mode_t mode, bool isLocalFile,
            bool showProgressInfo )
     :m_timer(0,"KRun::timer")
{
  init (url, window, "", mode, isLocalFile, showProgressInfo);
}

KRun::KRun( const KURL& url, TQWidget* window, const TQCString& asn, mode_t mode, bool isLocalFile,
            bool showProgressInfo )
     :m_timer(0,"KRun::timer")
{
  init (url, window, asn, mode, isLocalFile, showProgressInfo);
}

void KRun::init ( const KURL& url, TQWidget* window, const TQCString& asn, mode_t mode, bool isLocalFile,
                  bool showProgressInfo )
{
  m_bFault = false;
  m_bAutoDelete = true;
  m_bProgressInfo = showProgressInfo;
  m_bFinished = false;
  m_job = 0L;
  m_strURL = url;
  m_bScanFile = false;
  m_bIsDirectory = false;
  m_bIsLocalFile = isLocalFile;
  m_mode = mode;
  d = new KRunPrivate;
  d->m_runExecutables = true;
  d->m_window = window;
  d->m_asn = asn;
  setEnableExternalBrowser(true);

  // Start the timer. This means we will return to the event
  // loop and do initialization afterwards.
  // Reason: We must complete the constructor before we do anything else.
  m_bInit = true;
  connect( &m_timer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotTimeout() ) );
  m_timer.start( 0, true );
  kdDebug(7010) << " new KRun " << this << " " << url.prettyURL() << " timer=" << &m_timer << endl;

  kapp->ref();
}

void KRun::init()
{
  kdDebug(7010) << "INIT called" << endl;

  bool bypassErrorMessage = false;

  if (m_strURL.url().startsWith("$(")) {
      // check for environment variables and make necessary translations
      TQString aValue = m_strURL.url();
      int nDollarPos = aValue.find( '$' );

      while( nDollarPos != -1 && nDollarPos+1 < static_cast<int>(aValue.length())) {
        // there is at least one $
        if( (aValue)[nDollarPos+1] != '$' ) {
          uint nEndPos = nDollarPos+1;
          // the next character is no $
          TQString aVarName;
          if (aValue[nEndPos]=='{')
          {
            while ( (nEndPos <= aValue.length()) && (aValue[nEndPos]!='}') )
                nEndPos++;
            nEndPos++;
            aVarName = aValue.mid( nDollarPos+2, nEndPos-nDollarPos-3 );
          }
          else
          {
            while ( nEndPos <= aValue.length() && (aValue[nEndPos].isNumber()
                    || aValue[nEndPos].isLetter() || aValue[nEndPos]=='_' )  )
                nEndPos++;
            aVarName = aValue.mid( nDollarPos+1, nEndPos-nDollarPos-1 );
          }
          const char* pEnv = 0;
          if (!aVarName.isEmpty())
               pEnv = getenv( aVarName.ascii() );
          if( pEnv ) {
            // !!! Sergey A. Sukiyazov <corwin@micom.don.ru> !!!
            // A environment variables may contain values in 8bit
            // locale cpecified encoding or in UTF8 encoding.
            aValue.replace( nDollarPos, nEndPos-nDollarPos, KStringHandler::from8Bit( pEnv ) );
          } else
            aValue.remove( nDollarPos, nEndPos-nDollarPos );
        } else {
          // remove one of the dollar signs
          aValue.remove( nDollarPos, 1 );
          nDollarPos++;
        }
        nDollarPos = aValue.find( '$', nDollarPos );
      }
      m_strURL = KURL(aValue);
      bypassErrorMessage = true;
  }

  if ( !m_strURL.isValid() )
  {
    if (bypassErrorMessage == false) {
      d->m_showingError = true;
      KMessageBoxWrapper::error( d->m_window, i18n( "Malformed URL\n%1" ).arg( m_strURL.url() ) );
      d->m_showingError = false;
    }
    m_bFault = true;
    m_bFinished = true;
    m_timer.start( 0, true );
    return;
  }
  if ( !kapp->authorizeURLAction( "open", KURL(), m_strURL))
  {
    TQString msg = TDEIO::buildErrorString(TDEIO::ERR_ACCESS_DENIED, m_strURL.prettyURL());
    d->m_showingError = true;
    KMessageBoxWrapper::error( d->m_window, msg );
    d->m_showingError = false;
    m_bFault = true;
    m_bFinished = true;
    m_timer.start( 0, true );
    return;
  }

  if ( !m_bIsLocalFile && m_strURL.isLocalFile() )
    m_bIsLocalFile = true;

  TQString exec;
  if (m_strURL.protocol().startsWith("http"))
  {
    exec = d->m_externalBrowser;
  }

  if ( m_bIsLocalFile )
  {
    if ( m_mode == 0 )
    {
      KDE_struct_stat buff;
      if ( KDE_stat( TQFile::encodeName(m_strURL.path()), &buff ) == -1 )
      {
        d->m_showingError = true;
        KMessageBoxWrapper::error( d->m_window, i18n( "<qt>Unable to run the command specified. The file or folder <b>%1</b> does not exist.</qt>" ).arg( m_strURL.htmlURL() ) );
        d->m_showingError = false;
        m_bFault = true;
        m_bFinished = true;
        m_timer.start( 0, true );
        return;
      }
      m_mode = buff.st_mode;
    }

    KMimeType::Ptr mime = KMimeType::findByURL( m_strURL, m_mode, m_bIsLocalFile );
    assert( mime != 0L );
    kdDebug(7010) << "MIME TYPE is " << mime->name() << endl;
    foundMimeType( mime->name() );
    return;
  }
  else if ( !exec.isEmpty() || KProtocolInfo::isHelperProtocol( m_strURL ) ) {
    kdDebug(7010) << "Helper protocol" << endl;

    bool ok = false;
    KURL::List urls;
    if (!((m_strURL.protocol().startsWith("http")) && (m_strURL.url() == "http://default.browser")))
        urls.append( m_strURL );
    if (exec.isEmpty())
    {
       exec = KProtocolInfo::exec( m_strURL.protocol() );
       if (exec.isEmpty())
       {
          foundMimeType(KProtocolInfo::defaultMimetype(m_strURL));
          return;
       }
       run( exec, urls );
       ok = true;
    }
    else if (exec.startsWith("!"))
    {
       exec = exec.mid(1); // Literal command
       exec += " %u";
       run( exec, urls );
       ok = true;
    }
    else
    {
       KService::Ptr service = KService::serviceByStorageId( exec );
       if (service)
       {
          run( *service, urls, d->m_window, d->m_asn );
          ok = true;
       }
    }

    if (ok)
    {
       m_bFinished = true;
       // will emit the error and autodelete this
       m_timer.start( 0, true );
       return;
    }
  }

  if ((m_strURL.protocol().startsWith("http")) && (m_strURL.url() == "http://default.browser")) {
    KURL::List urls;
    run( "kfmclient openProfile webbrowsing", urls );
    m_bFinished = true;
    // will emit the error and autodelete this
    m_timer.start( 0, true );
    return;
  }

  // Did we already get the information that it is a directory ?
  if ( S_ISDIR( m_mode ) )
  {
    foundMimeType( "inode/directory" );
    return;
  }

  // Let's see whether it is a directory

  if ( !KProtocolInfo::supportsListing( m_strURL ) )
  {
    //kdDebug(7010) << "Protocol has no support for listing" << endl;
    // No support for listing => it can't be a directory (example: http)
    scanFile();
    return;
  }

  kdDebug(7010) << "Testing directory (stating)" << endl;

  // It may be a directory or a file, let's stat
  TDEIO::StatJob *job = TDEIO::stat( m_strURL, true, 0 /* no details */, m_bProgressInfo );
  job->setWindow (d->m_window);
  connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ),
           this, TQT_SLOT( slotStatResult( TDEIO::Job * ) ) );
  m_job = job;
  kdDebug(7010) << " Job " << job << " is about stating " << m_strURL.url() << endl;
}

KRun::~KRun()
{
  kdDebug(7010) << "KRun::~KRun() " << this << endl;
  m_timer.stop();
  killJob();
  kapp->deref();
  kdDebug(7010) << "KRun::~KRun() done " << this << endl;
  delete d;
}

void KRun::scanFile()
{
  kdDebug(7010) << "###### KRun::scanFile " << m_strURL.url() << endl;
  // First, let's check for well-known extensions
  // Not when there is a query in the URL, in any case.
  if ( m_strURL.query().isEmpty() )
  {
    KMimeType::Ptr mime = KMimeType::findByURL( m_strURL );
    assert( mime != 0L );
    if ( mime->name() != "application/octet-stream" || m_bIsLocalFile )
    {
      kdDebug(7010) << "Scanfile: MIME TYPE is " << mime->name() << endl;
      foundMimeType( mime->name() );
      return;
    }
  }

  // No mimetype found, and the URL is not local  (or fast mode not allowed).
  // We need to apply the 'TDEIO' method, i.e. either asking the server or
  // getting some data out of the file, to know what mimetype it is.

  if ( !KProtocolInfo::supportsReading( m_strURL ) )
  {
    kdError(7010) << "#### NO SUPPORT FOR READING!" << endl;
    m_bFault = true;
    m_bFinished = true;
    m_timer.start( 0, true );
    return;
  }
  kdDebug(7010) << this << " Scanning file " << m_strURL.url() << endl;

  TDEIO::TransferJob *job = TDEIO::get( m_strURL, false /*reload*/, m_bProgressInfo );
  job->setWindow (d->m_window);
  connect(job, TQT_SIGNAL( result(TDEIO::Job *)),
          this, TQT_SLOT( slotScanFinished(TDEIO::Job *)));
  connect(job, TQT_SIGNAL( mimetype(TDEIO::Job *, const TQString &)),
          this, TQT_SLOT( slotScanMimeType(TDEIO::Job *, const TQString &)));
  m_job = job;
  kdDebug(7010) << " Job " << job << " is about getting from " << m_strURL.url() << endl;
}

void KRun::slotTimeout()
{
  kdDebug(7010) << this << " slotTimeout called" << endl;
  if ( m_bInit )
  {
    m_bInit = false;
    init();
    return;
  }

  if ( m_bFault ) {
      emit error();
  }
  if ( m_bFinished ) {
      emit finished();
  }
  else
  {
    if ( m_bScanFile )
    {
      m_bScanFile = false;
      scanFile();
      return;
    }
    else if ( m_bIsDirectory )
    {
      m_bIsDirectory = false;
      foundMimeType( "inode/directory" );
      return;
    }
  }

  if ( m_bAutoDelete )
  {
    delete this;
    return;
  }
}

void KRun::slotStatResult( TDEIO::Job * job )
{
  m_job = 0L;
  if (job->error())
  {
    d->m_showingError = true;
    kdError(7010) << this << " ERROR " << job->error() << " " << job->errorString() << endl;
    job->showErrorDialog();
    //kdDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us" << endl;
    d->m_showingError = false;

    m_bFault = true;
    m_bFinished = true;

    // will emit the error and autodelete this
    m_timer.start( 0, true );

  } else {

    kdDebug(7010) << "Finished" << endl;
    if(!dynamic_cast<TDEIO::StatJob*>(job))
        kdFatal() << "job is a " << typeid(*job).name() << " should be a StatJob" << endl;

    TQString knownMimeType;
    TDEIO::UDSEntry entry = ((TDEIO::StatJob*)job)->statResult();
    TDEIO::UDSEntry::ConstIterator it = entry.begin();
    for( ; it != entry.end(); it++ ) {
        switch( (*it).m_uds ) {
        case TDEIO::UDS_FILE_TYPE:
            if ( S_ISDIR( (mode_t)((*it).m_long) ) )
                m_bIsDirectory = true; // it's a dir
            else
                m_bScanFile = true; // it's a file
            break;
        case TDEIO::UDS_MIME_TYPE: // mimetype already known? (e.g. print:/manager)
            knownMimeType = (*it).m_str;
            break;
        case TDEIO::UDS_LOCAL_PATH:
            d->m_localPath = (*it).m_str;
            break;
        default:
            break;
        }
    }
    if ( !knownMimeType.isEmpty() )
    {
        foundMimeType( knownMimeType );
        m_bFinished = true;
    }

    // We should have found something
    assert ( m_bScanFile || m_bIsDirectory );

    // Start the timer. Once we get the timer event this
    // protocol server is back in the pool and we can reuse it.
    // This gives better performance than starting a new slave
    m_timer.start( 0, true );
  }
}

void KRun::slotScanMimeType( TDEIO::Job *, const TQString &mimetype )
{
  if ( mimetype.isEmpty() )
    kdWarning(7010) << "KRun::slotScanFinished : MimetypeJob didn't find a mimetype! Probably a tdeioslave bug." << endl;
  foundMimeType( mimetype );
  m_job = 0;
}

void KRun::slotScanFinished( TDEIO::Job *job )
{
  m_job = 0;
  if (job->error())
  {
    d->m_showingError = true;
    kdError(7010) << this << " ERROR (stat) : " << job->error() << " " << job->errorString() << endl;
    job->showErrorDialog();
    //kdDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us" << endl;
    d->m_showingError = false;

    m_bFault = true;
    m_bFinished = true;

    // will emit the error and autodelete this
    m_timer.start( 0, true );
  }
}

void KRun::foundMimeType( const TQString& type )
{
  kdDebug(7010) << "Resulting mime type is " << type << endl;

/*
  // Automatically unzip stuff

  // Disabled since the new TDEIO doesn't have filters yet.

  if ( type == "application/x-gzip"  ||
       type == "application/x-bzip"  ||
       type == "application/x-bzip2"  )
  {
    KURL::List lst = KURL::split( m_strURL );
    if ( lst.isEmpty() )
    {
      TQString tmp = i18n( "Malformed URL" );
      tmp += "\n";
      tmp += m_strURL.url();
      KMessageBoxWrapper::error( 0L, tmp );
      return;
    }

    if ( type == "application/x-gzip" )
      lst.prepend( KURL( "gzip:/decompress" ) );
    else if ( type == "application/x-bzip" )
      lst.prepend( KURL( "bzip:/decompress" ) );
    else if ( type == "application/x-bzip2" )
      lst.prepend( KURL( "bzip2:/decompress" ) );
    else if ( type == "application/x-tar" )
      lst.prepend( KURL( "tar:/" ) );

    // Move the HTML style reference to the leftmost URL
    KURL::List::Iterator it = lst.begin();
    ++it;
    (*lst.begin()).setRef( (*it).ref() );
    (*it).setRef( TQString::null );

    // Create the new URL
    m_strURL = KURL::join( lst );

    kdDebug(7010) << "Now trying with " << debugString(m_strURL.url()) << endl;

    killJob();

    // We don't know if this is a file or a directory. Let's test this first.
    // (For instance a tar.gz is a directory contained inside a file)
    // It may be a directory or a file, let's stat
    TDEIO::StatJob *job = TDEIO::stat( m_strURL, m_bProgressInfo );
    connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ),
             this, TQT_SLOT( slotStatResult( TDEIO::Job * ) ) );
    m_job = job;

    return;
  }
*/
  TDEIO::TransferJob *job = ::tqqt_cast<TDEIO::TransferJob *>( m_job );
  if ( job )
  {
     job->putOnHold();
     TDEIO::Scheduler::publishSlaveOnHold();
     m_job = 0;
  }

  Q_ASSERT( !m_bFinished );

  // Suport for preferred service setting, see setPreferredService
  if ( !d->m_preferredService.isEmpty() ) {
      kdDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService << endl;
      KService::Ptr serv = KService::serviceByDesktopName( d->m_preferredService );
      if ( serv && serv->hasServiceType( type ) )
      {
          KURL::List lst;
          lst.append( m_strURL );
          m_bFinished = KRun::run( *serv, lst, d->m_window, d->m_asn );
          /// Note: the line above means that if that service failed, we'll
          /// go to runURL to maybe find another service, even though a dialog
          /// box was displayed. That's good if runURL tries another service,
          /// but it's not good if it tries the same one :}
      }
  }

  // Resolve .desktop files from media:/, remote:/, applications:/ etc.
  if ( ((type == "application/x-desktop") ||
      (type == "media/builtin-mydocuments") ||
      (type == "media/builtin-mycomputer") ||
      (type == "media/builtin-mynetworkplaces") ||
      (type == "media/builtin-printers") ||
      (type == "media/builtin-trash") ||
      (type == "media/builtin-webbrowser")) /* or inheriting? */ && (!d->m_localPath.isEmpty()) )
  {
    m_strURL = KURL();
    m_strURL.setPath( d->m_localPath );
  }

  if (!m_bFinished && KRun::runURL( m_strURL, type, d->m_window, d->m_asn, false, d->m_runExecutables, d->m_suggestedFileName )){
    m_bFinished = true;
  }
  else{
    m_bFinished = true;
     m_bFault = true;
  }

  m_timer.start( 0, true );
}

void KRun::killJob()
{
  if ( m_job )
  {
    kdDebug(7010) << "KRun::killJob run=" << this << " m_job=" << m_job << endl;
    m_job->kill();
    m_job = 0L;
  }
}

void KRun::abort()
{
  kdDebug(7010) << "KRun::abort " << this << " m_showingError=" << d->m_showingError << endl;
  killJob();
  // If we're showing an error message box, the rest will be done
  // after closing the msgbox -> don't autodelete nor emit signals now.
  if ( d->m_showingError )
    return;
  m_bFault = true;
  m_bFinished = true;
  m_bInit = false;
  m_bScanFile = false;

  // will emit the error and autodelete this
  m_timer.start( 0, true );
}

void KRun::setEnableExternalBrowser(bool b)
{
   if (b)
      d->m_externalBrowser = TDEConfigGroup(TDEGlobal::config(), "General").readEntry("BrowserApplication");
   else
      d->m_externalBrowser = TQString::null;
}

void KRun::setPreferredService( const TQString& desktopEntryName )
{
    d->m_preferredService = desktopEntryName;
}

void KRun::setRunExecutables(bool b)
{
    d->m_runExecutables = b;
}

void KRun::setSuggestedFileName( const TQString& fileName )
{
    d->m_suggestedFileName = fileName;
}

bool KRun::isExecutable( const TQString& serviceType )
{
    return ( serviceType == "application/x-desktop" ||
             serviceType == "media/builtin-mydocuments" ||
             serviceType == "media/builtin-mycomputer" ||
             serviceType == "media/builtin-mynetworkplaces" ||
             serviceType == "media/builtin-printers" ||
             serviceType == "media/builtin-trash" ||
             serviceType == "media/builtin-webbrowser" ||
             serviceType == "application/x-executable" ||
             serviceType == "application/x-msdos-program" ||
             serviceType == "application/x-shellscript" );
}

/****************/

pid_t
TDEProcessRunner::run(TDEProcess * p, const TQString & binName)
{
  return (new TDEProcessRunner(p, binName))->pid();
}

#ifdef Q_WS_X11
pid_t
TDEProcessRunner::run(TDEProcess * p, const TQString & binName, const TDEStartupInfoId& id )
{
  return (new TDEProcessRunner(p, binName, id))->pid();
}
#endif

TDEProcessRunner::TDEProcessRunner(TDEProcess * p, const TQString & _binName )
  : TQObject(),
    process_(p),
    binName( _binName )
{
  TQObject::connect(
      process_, TQT_SIGNAL(processExited(TDEProcess *)),
      this,     TQT_SLOT(slotProcessExited(TDEProcess *)));

  process_->start();
  if ( !process_->pid() )
      slotProcessExited( process_ );
}

#ifdef Q_WS_X11
TDEProcessRunner::TDEProcessRunner(TDEProcess * p, const TQString & _binName, const TDEStartupInfoId& id )
  : TQObject(),
    process_(p),
    binName( _binName ),
    id_( id )
{
  TQObject::connect(
      process_, TQT_SIGNAL(processExited(TDEProcess *)),
      this,     TQT_SLOT(slotProcessExited(TDEProcess *)));

  process_->start();
  if ( !process_->pid() )
      slotProcessExited( process_ );
}
#endif

TDEProcessRunner::~TDEProcessRunner()
{
  delete process_;
}

  pid_t
TDEProcessRunner::pid() const
{
  return process_->pid();
}

  void
TDEProcessRunner::slotProcessExited(TDEProcess * p)
{
  if (p != process_)
    return; // Eh ?

  kdDebug(7010) << "slotProcessExited " << binName << endl;
  kdDebug(7010) << "normalExit " << process_->normalExit() << endl;
  kdDebug(7010) << "exitStatus " << process_->exitStatus() << endl;
  bool showErr = process_->normalExit()
                 && ( process_->exitStatus() == 127 || process_->exitStatus() == 1 );
  if ( !binName.isEmpty() && ( showErr || process_->pid() == 0 ) )
  {
    // Often we get 1 (zsh, csh) or 127 (ksh, bash) because the binary doesn't exist.
    // We can't just rely on that, but it's a good hint.
    // Before assuming its really so, we'll try to find the binName
    // relatively to current directory,  and then in the PATH.
    if ( !TQFile( binName ).exists() && TDEStandardDirs::findExe( binName ).isEmpty() )
    {
      kapp->ref();
      KMessageBox::sorry( 0L, i18n("Could not find the program '%1'").arg( binName ) );
      kapp->deref();
    }
  }
#ifdef Q_WS_X11
  if( !id_.none())
  {
      TDEStartupInfoData data;
      data.addPid( pid()); // announce this pid for the startup notification has finished
      data.setHostname();
      TDEStartupInfo::sendFinish( id_, data );
  }
#endif
  deleteLater();
}

void KRun::virtual_hook( int, void* )
{ /*BASE::virtual_hook( id, data );*/ }

#include "krun.moc"
