/*
    This file is part of Konsole

    Copyright (C) 2006-2007 by Robert Knight <robertknight@gmail.com>
    Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de>

    Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008

    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
    02110-1301  USA.
*/

// Own
#include "Session.h"

// Standard
#include <assert.h>
#include <stdlib.h>

// Qt
#include <QtGui/QApplication>
#include <QtCore/QByteRef>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QRegExp>
#include <QtCore/QStringList>
#include <QtCore>

#include "Pty.h"
#include "TerminalDisplay.h"
#include "ShellCommand.h"
#include "Vt102Emulation.h"

using namespace Konsole;

int Session::lastSessionId = 0;

Session::Session() :
    _shellProcess(0)
   , _emulation(0)
   , _monitorActivity(false)
   , _monitorSilence(false)
   , _notifiedActivity(false)
   , _autoClose(true)
   , _wantedClose(false)
   , _silenceSeconds(10)
   , _addToUtmp(false)  // disabled by default because of a bug encountered on certain systems
                        // which caused Konsole to hang when closing a tab and then opening a new
                        // one.  A 'QProcess destroyed while still running' warning was being
                        // printed to the terminal.  Likely a problem in KPty::logout() 
                        // or KPty::login() which uses a QProcess to start /usr/bin/utempter 
   , _flowControl(true)
   , _fullScripting(false)
   , _sessionId(0)
//   , _zmodemBusy(false)
//   , _zmodemProc(0)
//   , _zmodemProgress(0)
   , _hasDarkBackground(false)
{
    //prepare DBus communication
//    new SessionAdaptor(this);
    _sessionId = ++lastSessionId;
//    QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/")+QString::number(_sessionId), this);

    //create teletype for I/O with shell process
    _shellProcess = new Pty();

    //create emulation backend
    _emulation = new Vt102Emulation();

    connect( _emulation, SIGNAL( titleChanged( int, const QString & ) ),
           this, SLOT( setUserTitle( int, const QString & ) ) );
    connect( _emulation, SIGNAL( stateSet(int) ),
           this, SLOT( activityStateSet(int) ) );
//    connect( _emulation, SIGNAL( zmodemDetected() ), this ,
//            SLOT( fireZModemDetected() ) );
    connect( _emulation, SIGNAL( changeTabTextColorRequest( int ) ),
           this, SIGNAL( changeTabTextColorRequest( int ) ) );
    connect( _emulation, SIGNAL(profileChangeCommandReceived(const QString&)),
           this, SIGNAL( profileChangeCommandReceived(const QString&)) );
    // TODO
    // connect( _emulation,SIGNAL(imageSizeChanged(int,int)) , this ,
    //        SLOT(onEmulationSizeChange(int,int)) );

    //connect teletype to emulation backend
    _shellProcess->setUtf8Mode(_emulation->utf8());

    connect( _shellProcess,SIGNAL(receivedData(const char*,int)),this,
            SLOT(onReceiveBlock(const char*,int)) );
    connect( _emulation,SIGNAL(sendData(const char*,int)),_shellProcess,
            SLOT(sendData(const char*,int)) );
    connect( _emulation,SIGNAL(lockPtyRequest(bool)),_shellProcess,SLOT(lockPty(bool)) );
    connect( _emulation,SIGNAL(useUtf8Request(bool)),_shellProcess,SLOT(setUtf8Mode(bool)) );


    connect( _shellProcess,SIGNAL(done(int)), this, SLOT(done(int)) );

    //setup timer for monitoring session activity
    _monitorTimer = new QTimer(this);
    _monitorTimer->setSingleShot(true);
    connect(_monitorTimer, SIGNAL(timeout()), this, SLOT(monitorTimerDone()));
}

WId Session::windowId() const
{
    // Returns a window ID for this session which is used
    // to set the WINDOWID environment variable in the shell
    // process.
    //
    // Sessions can have multiple views or no views, which means
    // that a single ID is not always going to be accurate.
    //
    // If there are no views, the window ID is just 0.  If
    // there are multiple views, then the window ID for the
    // top-level window which contains the first view is
    // returned

    if ( _views.count() == 0 )
       return 0;
    else
    {
        QWidget* window = _views.first();

        Q_ASSERT( window );

        while ( window->parentWidget() != 0 )
            window = window->parentWidget();

        return window->winId();
    }
}

void Session::setDarkBackground(bool darkBackground)
{
    _hasDarkBackground = darkBackground;
}
bool Session::hasDarkBackground() const
{
    return _hasDarkBackground;
}
bool Session::isRunning() const
{
    return _shellProcess->isRunning();
}

void Session::setCodec(QTextCodec* codec)
{
    emulation()->setCodec(codec);
}

void Session::setProgram(const QString& program)
{
    _program = ShellCommand::expand(program);
}
void Session::setInitialWorkingDirectory(const QString& dir)
{
    _initialWorkingDir = ShellCommand::expand(dir);
}
void Session::setArguments(const QStringList& arguments)
{
    _arguments = ShellCommand::expand(arguments);
}

QList<TerminalDisplay*> Session::views() const
{
    return _views;
}

void Session::addView(TerminalDisplay* widget)
{
     Q_ASSERT( !_views.contains(widget) );

    _views.append(widget);

    if ( _emulation != 0 )
    {
        // connect emulation - view signals and slots
        connect( widget , SIGNAL(keyPressedSignal(QKeyEvent*)) , _emulation ,
               SLOT(sendKeyEvent(QKeyEvent*)) );
        connect( widget , SIGNAL(mouseSignal(int,int,int,int)) , _emulation ,
               SLOT(sendMouseEvent(int,int,int,int)) );
        connect( widget , SIGNAL(sendStringToEmu(const char*)) , _emulation ,
               SLOT(sendString(const char*)) );

        // allow emulation to notify view when the foreground process
        // indicates whether or not it is interested in mouse signals
        connect( _emulation , SIGNAL(programUsesMouseChanged(bool)) , widget ,
               SLOT(setUsesMouse(bool)) );

        widget->setUsesMouse( _emulation->programUsesMouse() );

        widget->setScreenWindow(_emulation->createWindow());
    }

    //connect view signals and slots
    QObject::connect( widget ,SIGNAL(changedContentSizeSignal(int,int)),this,
                    SLOT(onViewSizeChange(int,int)));

    QObject::connect( widget ,SIGNAL(destroyed(QObject*)) , this ,
                    SLOT(viewDestroyed(QObject*)) );
//slot for close
    QObject::connect(this, SIGNAL(finished()), widget, SLOT(close()));		    
    
}

void Session::viewDestroyed(QObject* view)
{
    TerminalDisplay* display = (TerminalDisplay*)view;

    Q_ASSERT( _views.contains(display) );

    removeView(display);
}

void Session::removeView(TerminalDisplay* widget)
{
    _views.removeAll(widget);

	disconnect(widget,0,this,0);

    if ( _emulation != 0 )
    {
        // disconnect
        //  - key presses signals from widget
        //  - mouse activity signals from widget
        //  - string sending signals from widget
        //
        //  ... and any other signals connected in addView()
        disconnect( widget, 0, _emulation, 0);

        // disconnect state change signals emitted by emulation
        disconnect( _emulation , 0 , widget , 0);
    }

	// close the session automatically when the last view is removed
	if ( _views.count() == 0 )
	{
		close();
	}
}

void Session::run()
{
  //check that everything is in place to run the session
  if (_program.isEmpty())
      qDebug() << "Session::run() - program to run not set.";
  if (_arguments.isEmpty())
      qDebug() << "Session::run() - no command line arguments specified.";

  // Upon a KPty error, there is no description on what that error was...
  // Check to see if the given program is executable.
  QString exec = QFile::encodeName(_program);

  // if 'exec' is not specified, fall back to default shell.  if that 
  // is not set then fall back to /bin/sh
  if ( exec.isEmpty() )
      exec = getenv("SHELL");
  if ( exec.isEmpty() )
  	  exec = "/bin/sh";

  // if no arguments are specified, fall back to shell
  QStringList arguments =  _arguments.join(QChar(' ')).isEmpty() ?
                                    QStringList() << exec : _arguments;
  QString pexec = exec;

  if ( pexec.isEmpty() ) {
    qDebug()<<"can not execute "<<exec<<endl;
    QTimer::singleShot(1, this, SIGNAL(finished()));
    return;
  }

//  QString cwd_save = QDir::currentPath();
  QString cwd = QDir::currentPath();
  if (!_initialWorkingDir.isEmpty())
    _shellProcess->setWorkingDirectory(_initialWorkingDir);
  else
    _shellProcess->setWorkingDirectory(cwd);
//    _shellProcess->setWorkingDirectory(QDir::homePath());

  _shellProcess->setXonXoff(_flowControl);
  _shellProcess->setErase(_emulation->getErase());

  // this is not strictly accurate use of the COLORFGBG variable.  This does not
  // tell the terminal exactly which colors are being used, but instead approximates
  // the color scheme as "black on white" or "white on black" depending on whether
  // the background color is deemed dark or not
  QString backgroundColorHint = _hasDarkBackground ? "COLORFGBG=15;0" : "COLORFGBG=0;15";

  int result = _shellProcess->start(QFile::encodeName(_program),
                                  arguments,
                                  _environment << backgroundColorHint,
                                  windowId(),
                                  _addToUtmp);

  if (result < 0)
  {
    return;
  }

  _shellProcess->setWriteable(false);  // We are reachable via kwrited.

  emit started();
}

void Session::setUserTitle( int what, const QString &caption )
{
    //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle )
	bool modified = false;

    // (btw: what=0 changes _userTitle and icon, what=1 only icon, what=2 only _nameTitle
    if ((what == 0) || (what == 2)) 
    {
       	if ( _userTitle != caption ) {
			_userTitle = caption;
			modified = true;
		}
    }

    if ((what == 0) || (what == 1))
	{
		if ( _iconText != caption ) {
       		_iconText = caption;
			modified = true;
		}
	}

    if (what == 11) 
    {
      QString colorString = caption.section(';',0,0);
      qDebug() << __FILE__ << __LINE__ << ": setting background colour to " << colorString;
      QColor backColor = QColor(colorString);
      if (backColor.isValid()){// change color via \033]11;Color\007
          if (backColor != _modifiedBackground)
          {
              _modifiedBackground = backColor;

              // bail out here until the code to connect the terminal display
              // to the changeBackgroundColor() signal has been written
              // and tested - just so we don't forget to do this.
              Q_ASSERT( 0 );

              emit changeBackgroundColorRequest(backColor);
          }
      }
    }

	if (what == 30) 
    {
		if ( _nameTitle != caption ) {
       		setTitle(Session::NameRole,caption);
			return;
		}
	}

    if (what == 31) 
    {
       QString cwd=caption;
       cwd=cwd.replace( QRegExp("^~"), QDir::homePath() );
       emit openUrlRequest(cwd);
	}

    // change icon via \033]32;Icon\007
    if (what == 32) 
    { 
    	if ( _iconName != caption ) {
	   		_iconName = caption;

			modified = true;
		}
    }

    if (what == 50) 
    {
        emit profileChangeCommandReceived(caption);
        return;
    }

	if ( modified )
    	emit titleChanged();
}

QString Session::userTitle() const
{
    return _userTitle;
}
void Session::setTabTitleFormat(TabTitleContext context , const QString& format)
{
    if ( context == LocalTabTitle )
        _localTabTitleFormat = format;
    else if ( context == RemoteTabTitle )
        _remoteTabTitleFormat = format;
}
QString Session::tabTitleFormat(TabTitleContext context) const
{
    if ( context == LocalTabTitle )
        return _localTabTitleFormat;
    else if ( context == RemoteTabTitle )
        return _remoteTabTitleFormat;

    return QString();
}

void Session::monitorTimerDone()
{
  //FIXME: The idea here is that the notification popup will appear to tell the user than output from
  //the terminal has stopped and the popup will disappear when the user activates the session.
  //
  //This breaks with the addition of multiple views of a session.  The popup should disappear
  //when any of the views of the session becomes active
  

  //FIXME: Make message text for this notification and the activity notification more descriptive.	
  if (_monitorSilence) {
//    KNotification::event("Silence", ("Silence in session '%1'", _nameTitle), QPixmap(),
//                    QApplication::activeWindow(),
//                    KNotification::CloseWhenWidgetActivated);
    emit stateChanged(NOTIFYSILENCE);
  }
  else
  {
    emit stateChanged(NOTIFYNORMAL);
  }

  _notifiedActivity=false;
}

void Session::activityStateSet(int state)
{
  if (state==NOTIFYBELL)
  {
      QString s; s.sprintf("Bell in session '%s'",_nameTitle.toAscii().data());
      
      emit bellRequest( s );
  }
  else if (state==NOTIFYACTIVITY)
  {
    if (_monitorSilence) {
      _monitorTimer->start(_silenceSeconds*1000);
    }

    if ( _monitorActivity ) {
      //FIXME:  See comments in Session::monitorTimerDone()
      if (!_notifiedActivity) {
//        KNotification::event("Activity", ("Activity in session '%1'", _nameTitle), QPixmap(),
//                        QApplication::activeWindow(),
//        KNotification::CloseWhenWidgetActivated);
        _notifiedActivity=true;
      }
    }
  }

  if ( state==NOTIFYACTIVITY && !_monitorActivity )
          state = NOTIFYNORMAL;
  if ( state==NOTIFYSILENCE && !_monitorSilence )
          state = NOTIFYNORMAL;

  emit stateChanged(state);
}

void Session::onViewSizeChange(int /*height*/, int /*width*/)
{
  updateTerminalSize();
}
void Session::onEmulationSizeChange(int lines , int columns)
{
  setSize( QSize(lines,columns) );
}

void Session::updateTerminalSize()
{
    QListIterator<TerminalDisplay*> viewIter(_views);

    int minLines = -1;
    int minColumns = -1;

    // minimum number of lines and columns that views require for
    // their size to be taken into consideration ( to avoid problems
    // with new view widgets which haven't yet been set to their correct size )
    const int VIEW_LINES_THRESHOLD = 2;
    const int VIEW_COLUMNS_THRESHOLD = 2;

    //select largest number of lines and columns that will fit in all visible views
    while ( viewIter.hasNext() )
    {
        TerminalDisplay* view = viewIter.next();
        if ( view->isHidden() == false &&
             view->lines() >= VIEW_LINES_THRESHOLD &&
             view->columns() >= VIEW_COLUMNS_THRESHOLD )
        {
            minLines = (minLines == -1) ? view->lines() : qMin( minLines , view->lines() );
            minColumns = (minColumns == -1) ? view->columns() : qMin( minColumns , view->columns() );
        }
    }

    // backend emulation must have a _terminal of at least 1 column x 1 line in size
    if ( minLines > 0 && minColumns > 0 )
    {
        _emulation->setImageSize( minLines , minColumns );
        _shellProcess->setWindowSize( minLines , minColumns );
    }
}

void Session::refresh()
{
    // attempt to get the shell process to redraw the display
    //
    // this requires the program running in the shell
    // to cooperate by sending an update in response to
    // a window size change
    //
    // the window size is changed twice, first made slightly larger and then
    // resized back to its normal size so that there is actually a change
    // in the window size (some shells do nothing if the
    // new and old sizes are the same)
    //
    // if there is a more 'correct' way to do this, please
    // send an email with method or patches to konsole-devel@kde.org

    const QSize existingSize = _shellProcess->windowSize();
    _shellProcess->setWindowSize(existingSize.height(),existingSize.width()+1);
    _shellProcess->setWindowSize(existingSize.height(),existingSize.width());
}

bool Session::sendSignal(int signal)
{
  return _shellProcess->kill(signal);
}

void Session::close()
{
  _autoClose = true;
  _wantedClose = true;
  if (!_shellProcess->isRunning() || !sendSignal(SIGHUP))
  {
     // Forced close.
     QTimer::singleShot(1, this, SIGNAL(finished()));
  }
}

void Session::sendText(const QString &text) const
{
  _emulation->sendText(text);
}

Session::~Session()
{
  delete _emulation;
  delete _shellProcess;
//  delete _zmodemProc;
}

void Session::setProfileKey(const QString& key)
{
    _profileKey = key;
    emit profileChanged(key);
}
QString Session::profileKey() const { return _profileKey; }

void Session::done(int exitStatus)
{
  if (!_autoClose)
  {
    _userTitle = ("<Finished>");
    emit titleChanged();
    return;
  }
  if (!_wantedClose && (exitStatus || _shellProcess->signalled()))
  {
    QString message;

    if (_shellProcess->normalExit())
      message.sprintf ("Session '%s' exited with status %d.", _nameTitle.toAscii().data(), exitStatus);
    else if (_shellProcess->signalled())
    {
      if (_shellProcess->coreDumped())
      {    

        message.sprintf("Session '%s' exited with signal %d and dumped core.", _nameTitle.toAscii().data(), _shellProcess->exitSignal());
      }
      else { 
        message.sprintf("Session '%s' exited with signal %d.", _nameTitle.toAscii().data(), _shellProcess->exitSignal());
      }
    }
    else
        message.sprintf ("Session '%s' exited unexpectedly.", _nameTitle.toAscii().data());

    //FIXME: See comments in Session::monitorTimerDone()
//    KNotification::event("Finished", message , QPixmap(),
//                         QApplication::activeWindow(),
//                         KNotification::CloseWhenWidgetActivated);
  }
  emit finished();
}

Emulation* Session::emulation() const
{
  return _emulation;
}

QString Session::keyBindings() const
{
  return _emulation->keyBindings();
}

QStringList Session::environment() const
{
  return _environment;
}

void Session::setEnvironment(const QStringList& environment)
{
    _environment = environment;
}

int Session::sessionId() const
{
  return _sessionId;
}

void Session::setKeyBindings(const QString &id)
{
  _emulation->setKeyBindings(id);
}

void Session::setTitle(TitleRole role , const QString& newTitle)
{
    if ( title(role) != newTitle )
    {
        if ( role == NameRole )
            _nameTitle = newTitle;
        else if ( role == DisplayedTitleRole )
            _displayTitle = newTitle;

        emit titleChanged();
    }
}

QString Session::title(TitleRole role) const
{
    if ( role == NameRole )
        return _nameTitle;
    else if ( role == DisplayedTitleRole )
        return _displayTitle;
    else
        return QString();
}

void Session::setIconName(const QString& iconName)
{
    if ( iconName != _iconName )
    {
        _iconName = iconName;
        emit titleChanged();
    }
}

void Session::setIconText(const QString& iconText)
{
  _iconText = iconText;
  //kDebug(1211)<<"Session setIconText " <<  _iconText;
}

QString Session::iconName() const
{
  return _iconName;
}

QString Session::iconText() const
{
  return _iconText;
}

void Session::setHistoryType(const HistoryType &hType)
{
  _emulation->setHistory(hType);
}

const HistoryType& Session::historyType() const
{
  return _emulation->history();
}

void Session::clearHistory()
{
    _emulation->clearHistory();
}

QStringList Session::arguments() const
{
  return _arguments;
}

QString Session::program() const
{
  return _program;
}

// unused currently
bool Session::isMonitorActivity() const { return _monitorActivity; }
// unused currently
bool Session::isMonitorSilence()  const { return _monitorSilence; }

void Session::setMonitorActivity(bool _monitor)
{
  _monitorActivity=_monitor;
  _notifiedActivity=false;

  activityStateSet(NOTIFYNORMAL);
}

void Session::setMonitorSilence(bool _monitor)
{
  if (_monitorSilence==_monitor)
    return;

  _monitorSilence=_monitor;
  if (_monitorSilence)
  {
    _monitorTimer->start(_silenceSeconds*1000);
  }
  else
    _monitorTimer->stop();

  activityStateSet(NOTIFYNORMAL);
}

void Session::setMonitorSilenceSeconds(int seconds)
{
  _silenceSeconds=seconds;
  if (_monitorSilence) {
    _monitorTimer->start(_silenceSeconds*1000);
  }
}

void Session::setAddToUtmp(bool set)
{
  _addToUtmp = set;
}

void Session::setFlowControlEnabled(bool enabled)
{
  if (_flowControl == enabled)
  	return;

  _flowControl = enabled;

  if (_shellProcess)  
	_shellProcess->setXonXoff(_flowControl);
  
  emit flowControlEnabledChanged(enabled);
}
bool Session::flowControlEnabled() const
{
	return _flowControl;
}
//void Session::fireZModemDetected()
//{
//  if (!_zmodemBusy)
//  {
//    QTimer::singleShot(10, this, SIGNAL(zmodemDetected()));
//    _zmodemBusy = true;
//  }
//}

//void Session::cancelZModem()
//{
//  _shellProcess->sendData("\030\030\030\030", 4); // Abort
//  _zmodemBusy = false;
//}

//void Session::startZModem(const QString &zmodem, const QString &dir, const QStringList &list)
//{
//  _zmodemBusy = true;
//  _zmodemProc = new KProcess();
//  _zmodemProc->setOutputChannelMode( KProcess::SeparateChannels );
//
//  *_zmodemProc << zmodem << "-v" << list;
//
//  if (!dir.isEmpty())
//     _zmodemProc->setWorkingDirectory(dir);
//
//  _zmodemProc->start();
//
//  connect(_zmodemProc,SIGNAL (readyReadStandardOutput()),
//          this, SLOT(zmodemReadAndSendBlock()));
//  connect(_zmodemProc,SIGNAL (readyReadStandardError()),
//          this, SLOT(zmodemReadStatus()));
//  connect(_zmodemProc,SIGNAL (finished(int,QProcess::ExitStatus)),
//          this, SLOT(zmodemFinished()));
//
//  disconnect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) );
//  connect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(zmodemRcvBlock(const char*,int)) );
//
//  _zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false,
//                                    i18n("ZModem Progress"));
//
//  connect(_zmodemProgress, SIGNAL(user1Clicked()),
//          this, SLOT(zmodemDone()));
//
//  _zmodemProgress->show();
//}

/*void Session::zmodemReadAndSendBlock()
{
  _zmodemProc->setReadChannel( QProcess::StandardOutput );
  QByteArray data = _zmodemProc->readAll();

  if ( data.count() == 0 )
      return;

  _shellProcess->sendData(data.constData(),data.count());
}
*/
/*
void Session::zmodemReadStatus()
{
  _zmodemProc->setReadChannel( QProcess::StandardError );
  QByteArray msg = _zmodemProc->readAll();
  while(!msg.isEmpty())
  {
     int i = msg.indexOf('\015');
     int j = msg.indexOf('\012');
     QByteArray txt;
     if ((i != -1) && ((j == -1) || (i < j)))
     {
       msg = msg.mid(i+1);
     }
     else if (j != -1)
     {
       txt = msg.left(j);
       msg = msg.mid(j+1);
     }
     else
     {
       txt = msg;
       msg.truncate(0);
     }
     if (!txt.isEmpty())
       _zmodemProgress->addProgressText(QString::fromLocal8Bit(txt));
  }
}
*/
/*
void Session::zmodemRcvBlock(const char *data, int len)
{
  QByteArray ba( data, len );

  _zmodemProc->write( ba );
}
*/
/*
void Session::zmodemFinished()
{
  if (_zmodemProc)
  {
    delete _zmodemProc;
    _zmodemProc = 0;
    _zmodemBusy = false;

    disconnect( _shellProcess,SIGNAL(block_in(const char*,int)), this ,SLOT(zmodemRcvBlock(const char*,int)) );
    connect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) );

    _shellProcess->sendData("\030\030\030\030", 4); // Abort
    _shellProcess->sendData("\001\013\n", 3); // Try to get prompt back
    _zmodemProgress->transferDone();
  }
}
*/
void Session::onReceiveBlock( const char* buf, int len )
{
    _emulation->receiveData( buf, len );
    emit receivedData( QString::fromLatin1( buf, len ) );
}

QSize Session::size()
{
  return _emulation->imageSize();
}

void Session::setSize(const QSize& size)
{
  if ((size.width() <= 1) || (size.height() <= 1))
     return;

  emit resizeRequest(size);
}
int Session::foregroundProcessId() const
{
    return _shellProcess->foregroundProcessGroup();
}
int Session::processId() const
{
    return _shellProcess->pid();
}

SessionGroup::SessionGroup()
    : _masterMode(0)
{
}
SessionGroup::~SessionGroup()
{
    // disconnect all
    connectAll(false);
}
int SessionGroup::masterMode() const { return _masterMode; }
QList<Session*> SessionGroup::sessions() const { return _sessions.keys(); }
bool SessionGroup::masterStatus(Session* session) const { return _sessions[session]; }

void SessionGroup::addSession(Session* session)
{
    _sessions.insert(session,false);

    QListIterator<Session*> masterIter(masters());

    while ( masterIter.hasNext() )
        connectPair(masterIter.next(),session);
}
void SessionGroup::removeSession(Session* session)
{
    setMasterStatus(session,false);

    QListIterator<Session*> masterIter(masters());

    while ( masterIter.hasNext() )
        disconnectPair(masterIter.next(),session);

    _sessions.remove(session);
}
void SessionGroup::setMasterMode(int mode)
{
   _masterMode = mode;

   connectAll(false);
   connectAll(true);
}
QList<Session*> SessionGroup::masters() const
{
    return _sessions.keys(true);
}
void SessionGroup::connectAll(bool connect)
{
    QListIterator<Session*> masterIter(masters());

    while ( masterIter.hasNext() )
    {
        Session* master = masterIter.next();

        QListIterator<Session*> otherIter(_sessions.keys());
        while ( otherIter.hasNext() )
        {
            Session* other = otherIter.next();

            if ( other != master )
            {
                if ( connect )
                    connectPair(master,other);
                else
                    disconnectPair(master,other);
            }
        }
    }
}
void SessionGroup::setMasterStatus(Session* session , bool master)
{
    bool wasMaster = _sessions[session];
    _sessions[session] = master;

    if ( wasMaster==master )
      return;

    QListIterator<Session*> iter(_sessions.keys());
    while ( iter.hasNext() )
    {
        Session* other = iter.next();

        if ( other != session )
        {
            if ( master )
                connectPair(session,other);
            else
                disconnectPair(session,other);
        }
    }
}
void SessionGroup::connectPair(Session* master , Session* other)
{
//    qDebug() << k_funcinfo;

    if ( _masterMode & CopyInputToAll )
    {
        qDebug() << "Connection session " << master->nameTitle() << "to" << other->nameTitle();

        connect( master->emulation() , SIGNAL(sendData(const char*,int)) , other->emulation() ,
                 SLOT(sendString(const char*,int)) );
    }
}
void SessionGroup::disconnectPair(Session* master , Session* other)
{
//    qDebug() << k_funcinfo;

    if ( _masterMode & CopyInputToAll )
    {
        qDebug() << "Disconnecting session " << master->nameTitle() << "from" << other->nameTitle();

        disconnect( master->emulation() , SIGNAL(sendData(const char*,int)) , other->emulation() ,
                SLOT(sendString(const char*,int)) );
    }
}

//#include "moc_Session.cpp"
