Session.cpp 33.7 KB
Newer Older
1
/*
2
    This file is part of Konsole
3

4 5
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
    Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
6 7

    This program is free software; you can redistribute it and/or modify
8
    it under the terms of the GNU General Public License as published by
9 10 11 12 13 14 15 16 17 18 19 20 21
    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.
*/
22

23 24 25
// Own
#include "Session.h"

26
// Standard
27 28
#include <assert.h>
#include <stdlib.h>
29
#include <signal.h>
30

31
// Qt
Dirk Mueller's avatar
Dirk Mueller committed
32 33 34 35 36 37
#include <QtGui/QApplication>
#include <QtCore/QByteRef>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QRegExp>
#include <QtCore/QStringList>
38
#include <QtDBus/QtDBus>
Dirk Mueller's avatar
Dirk Mueller committed
39
#include <QtCore/QDate>
40

41
// KDE
42 43 44 45
#include <KDebug>
#include <KLocale>
#include <KMessageBox>
#include <KNotification>
46
#include <KProcess>
47
#include <KRun>
48
#include <kshell.h>
49
#include <KStandardDirs>
50
#include <KPtyDevice>
Robert Knight's avatar
 
Robert Knight committed
51
#include <KUrl>
52

53 54
// Konsole
#include <config-konsole.h>
55 56
#include <sessionadaptor.h>

Robert Knight's avatar
 
Robert Knight committed
57
#include "ProcessInfo.h"
58 59
#include "Pty.h"
#include "TerminalDisplay.h"
60
#include "ShellCommand.h"
61
#include "Vt102Emulation.h"
62
#include "ZModemDialog.h"
63

64
using namespace Konsole;
65

66 67
int Session::lastSessionId = 0;

68 69 70
Session::Session(QObject* parent) :
   QObject(parent)
   , _shellProcess(0)
71
   , _emulation(0)
72 73 74 75 76
   , _monitorActivity(false)
   , _monitorSilence(false)
   , _notifiedActivity(false)
   , _autoClose(true)
   , _wantedClose(false)
77
   , _silenceSeconds(10)
Robert Knight's avatar
Robert Knight committed
78
   , _addToUtmp(true)  
79 80
   , _flowControl(true)
   , _fullScripting(false)
81
   , _sessionId(0)
Robert Knight's avatar
 
Robert Knight committed
82 83 84
   , _sessionProcessInfo(0)
   , _foregroundProcessInfo(0)
   , _foregroundPid(0)
85 86 87
   , _zmodemBusy(false)
   , _zmodemProc(0)
   , _zmodemProgress(0)
88
   , _hasDarkBackground(false)
Robert Knight's avatar
 
Robert Knight committed
89 90
{
    //prepare DBus communication
91
    new SessionAdaptor(this);
92
    _sessionId = ++lastSessionId;
93
    QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/")+QString::number(_sessionId), this);
Robert Knight's avatar
 
Robert Knight committed
94 95

    //create emulation backend
96
    _emulation = new Vt102Emulation();
Robert Knight's avatar
 
Robert Knight committed
97

98
    connect( _emulation, SIGNAL( titleChanged( int, const QString & ) ),
Robert Knight's avatar
 
Robert Knight committed
99
           this, SLOT( setUserTitle( int, const QString & ) ) );
100 101
    connect( _emulation, SIGNAL( stateSet(int) ),
           this, SLOT( activityStateSet(int) ) );
102
    connect( _emulation, SIGNAL( zmodemDetected() ), this ,
103
            SLOT( fireZModemDetected() ) );
104
    connect( _emulation, SIGNAL( changeTabTextColorRequest( int ) ),
105
           this, SIGNAL( changeTabTextColorRequest( int ) ) );
106 107
    connect( _emulation, SIGNAL(profileChangeCommandReceived(const QString&)),
           this, SIGNAL( profileChangeCommandReceived(const QString&)) );
Robert Knight's avatar
 
Robert Knight committed
108 109 110
    connect( _emulation, SIGNAL(flowControlKeyPressed(bool)) , this, 
             SLOT(updateFlowControlState(bool)) );

111 112
    //create new teletype for I/O with shell process
    openTeletype(-1);
113

114 115 116 117 118 119 120 121
    //setup timer for monitoring session activity
    _monitorTimer = new QTimer(this);
    _monitorTimer->setSingleShot(true);
    connect(_monitorTimer, SIGNAL(timeout()), this, SLOT(monitorTimerDone()));
}

void Session::openTeletype(int fd)
{
122 123 124 125 126
    if (_shellProcess && isRunning())
    {
        kWarning() << "Attempted to open teletype in a running session.";
        return;
    }
127

128
    delete _shellProcess;
129

130 131 132 133
    if (fd < 0)
        _shellProcess = new Pty();
    else
        _shellProcess = new Pty(fd);
134

135
    _shellProcess->setUtf8Mode(_emulation->utf8());
136 137

    //connect teletype to emulation backend
138
    connect( _shellProcess,SIGNAL(receivedData(const char*,int)),this,
139
            SLOT(onReceiveBlock(const char*,int)) );
140
    connect( _emulation,SIGNAL(sendData(const char*,int)),_shellProcess,
141
            SLOT(sendData(const char*,int)) );
142
    connect( _emulation,SIGNAL(lockPtyRequest(bool)),_shellProcess,SLOT(lockPty(bool)) );
143
    connect( _emulation,SIGNAL(useUtf8Request(bool)),_shellProcess,SLOT(setUtf8Mode(bool)) );
144
    connect( _shellProcess,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(done(int)) );
145
    connect( _emulation,SIGNAL(imageSizeChanged(int,int)),this,SLOT(updateWindowSize(int,int)) );
Robert Knight's avatar
 
Robert Knight committed
146 147
}

148 149 150 151 152 153 154 155 156
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.
    //
157 158
    // If there are no views, the window ID is just 0.  If
    // there are multiple views, then the window ID for the
159 160 161 162 163 164 165 166
    // top-level window which contains the first view is
    // returned

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

168 169 170 171 172 173 174 175 176
        Q_ASSERT( window );

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

        return window->winId();
    }
}

177 178 179 180 181 182 183 184
void Session::setDarkBackground(bool darkBackground)
{
    _hasDarkBackground = darkBackground;
}
bool Session::hasDarkBackground() const
{
    return _hasDarkBackground;
}
185
bool Session::isRunning() const
186
{
187
    return _shellProcess->state() == QProcess::Running;
188 189
}

190 191 192 193 194
void Session::setCodec(QTextCodec* codec)
{
    emulation()->setCodec(codec);
}

195
void Session::setProgram(const QString& program)
Robert Knight's avatar
 
Robert Knight committed
196
{
197 198 199 200 201
    _program = ShellCommand::expand(program);
}
void Session::setInitialWorkingDirectory(const QString& dir)
{
    _initialWorkingDir = ShellCommand::expand(dir);
Robert Knight's avatar
 
Robert Knight committed
202
}
203
void Session::setArguments(const QStringList& arguments)
Robert Knight's avatar
 
Robert Knight committed
204
{
205
    _arguments = ShellCommand::expand(arguments);
Robert Knight's avatar
 
Robert Knight committed
206 207
}

Robert Knight's avatar
 
Robert Knight committed
208 209 210 211 212 213 214 215 216 217 218 219 220
QString Session::currentWorkingDirectory()
{
    // only returned cached value
    if (_currentWorkingDir.isEmpty()) updateWorkingDirectory();
    return _currentWorkingDir;
}
ProcessInfo* Session::updateWorkingDirectory()
{
    ProcessInfo *process = getProcessInfo();
    _currentWorkingDir = process->validCurrentDir();
    return process;
}

221
QList<TerminalDisplay*> Session::views() const
222 223 224 225
{
    return _views;
}

226
void Session::addView(TerminalDisplay* widget)
227 228 229 230 231
{
     Q_ASSERT( !_views.contains(widget) );

    _views.append(widget);

232
    if ( _emulation != 0 )
Robert Knight's avatar
 
Robert Knight committed
233 234 235
    {
        // connect emulation - view signals and slots
        connect( widget , SIGNAL(keyPressedSignal(QKeyEvent*)) , _emulation ,
236
               SLOT(sendKeyEvent(QKeyEvent*)) );
237
        connect( widget , SIGNAL(mouseSignal(int,int,int,int)) , _emulation ,
238
               SLOT(sendMouseEvent(int,int,int,int)) );
Robert Knight's avatar
 
Robert Knight committed
239
        connect( widget , SIGNAL(sendStringToEmu(const char*)) , _emulation ,
240
               SLOT(sendString(const char*)) );
Robert Knight's avatar
 
Robert Knight committed
241

242
        // allow emulation to notify view when the foreground process
243
        // indicates whether or not it is interested in mouse signals
244
        connect( _emulation , SIGNAL(programUsesMouseChanged(bool)) , widget ,
Robert Knight's avatar
 
Robert Knight committed
245
               SLOT(setUsesMouse(bool)) );
246 247

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

249
        widget->setScreenWindow(_emulation->createWindow());
Robert Knight's avatar
 
Robert Knight committed
250
    }
Robert Knight's avatar
 
Robert Knight committed
251

Robert Knight's avatar
 
Robert Knight committed
252
    //connect view signals and slots
253
    QObject::connect( widget ,SIGNAL(changedContentSizeSignal(int,int)),this,
254
                    SLOT(onViewSizeChange(int,int)));
255

256
    QObject::connect( widget ,SIGNAL(destroyed(QObject*)) , this ,
257
                    SLOT(viewDestroyed(QObject*)) );
258 259
}

260
void Session::viewDestroyed(QObject* view)
261
{
262
    TerminalDisplay* display = (TerminalDisplay*)view;
263

264 265 266
    Q_ASSERT( _views.contains(display) );

    removeView(display);
267 268
}

269
void Session::removeView(TerminalDisplay* widget)
270 271 272
{
    _views.removeAll(widget);

273
    disconnect(widget,0,this,0);
274

275
    if ( _emulation != 0 )
Robert Knight's avatar
 
Robert Knight committed
276 277
    {
        // disconnect
278
        //  - key presses signals from widget
Robert Knight's avatar
 
Robert Knight committed
279 280 281 282 283
        //  - mouse activity signals from widget
        //  - string sending signals from widget
        //
        //  ... and any other signals connected in addView()
        disconnect( widget, 0, _emulation, 0);
284

Robert Knight's avatar
 
Robert Knight committed
285 286 287
        // disconnect state change signals emitted by emulation
        disconnect( _emulation , 0 , widget , 0);
    }
288

289 290 291 292 293
    // close the session automatically when the last view is removed
    if ( _views.count() == 0 )
    {
        close();
    }
294 295
}

296
QString Session::checkProgram(const QString& program) const
297
{
298 299
  // Upon a KPty error, there is no description on what that error was...
  // Check to see if the given program is executable.
300 301 302
  QString exec = QFile::encodeName(program);

  if (exec.isEmpty())
303
      return QString();
304

305 306
  // if 'exec' is not specified, fall back to default shell.  if that 
  // is not set then fall back to /bin/sh
307
  if ( exec.isEmpty() )
308
      exec = qgetenv("SHELL");
309
  if ( exec.isEmpty() )
310
        exec = "/bin/sh";
311

312 313 314
  exec = KRun::binaryName(exec, false);
  exec = KShell::tildeExpand(exec);
  QString pexec = KGlobal::dirs()->findExe(exec);
315 316
  if ( pexec.isEmpty() ) 
  {
317
      kError() << i18n("Could not find binary: ") << exec;
318 319 320 321 322 323 324 325
    return QString();
  }

  return exec;
}

void Session::terminalWarning(const QString& message)
{
326 327
    static const QByteArray warningText = i18n("Warning: ").toLocal8Bit(); 
    QByteArray messageText = message.toLocal8Bit();
328

329
    static const char* redPenOn = "\033[1m\033[31m";
330
    static const char* redPenOff = "\033[0m";
331 332 333 334

    _emulation->receiveData(redPenOn,strlen(redPenOn));
    _emulation->receiveData("\n\r\n\r",4);
    _emulation->receiveData(warningText.constData(),strlen(warningText.constData()));
335 336 337
    _emulation->receiveData(messageText.constData(),strlen(messageText.constData()));
    _emulation->receiveData("\n\r\n\r",4);
    _emulation->receiveData(redPenOff,strlen(redPenOff));
338 339 340 341 342
}
void Session::run()
{
  //check that everything is in place to run the session
  if (_program.isEmpty())
Robert Knight's avatar
 
Robert Knight committed
343
  {
344
      kDebug() << "Session::run() - program to run not set.";
Robert Knight's avatar
 
Robert Knight committed
345
  }
346
  if (_arguments.isEmpty())
Robert Knight's avatar
 
Robert Knight committed
347
  {
348
      kDebug() << "Session::run() - no command line arguments specified.";
Robert Knight's avatar
 
Robert Knight committed
349
  }
350 351

  const int CHOICE_COUNT = 3;
352
  QString programs[CHOICE_COUNT] = {_program,qgetenv("SHELL"),"/bin/sh"};
353 354 355 356
  QString exec;
  int choice = 0;
  while (choice < CHOICE_COUNT)
  {
357 358 359 360 361
    exec = checkProgram(programs[choice]);
    if (exec.isEmpty())
        choice++;
    else
        break;
362
  }
363

Robert Knight's avatar
Robert Knight committed
364
  // if a program was specified via setProgram(), but it couldn't be found, print a warning
365 366
  if (choice != 0 && choice < CHOICE_COUNT && !_program.isEmpty())
  {
367
      terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.",_program,exec)); 
368
  }
Robert Knight's avatar
Robert Knight committed
369
  // if none of the choices are available, print a warning
370 371
  else if (choice == CHOICE_COUNT)
  {
372 373
      terminalWarning(i18n("Could not find an interactive shell to start."));
      return;
374 375 376 377
  }
  
  // if no arguments are specified, fall back to program name
  QStringList arguments = _arguments.join(QChar(' ')).isEmpty() ?
378
                                                   QStringList() << exec : _arguments;
379

380
  QString dbusService = QDBusConnection::sessionBus().baseService();
381
  if (!_initialWorkingDir.isEmpty())
382
    _shellProcess->setWorkingDirectory(_initialWorkingDir);
Thomas Zander's avatar
Thomas Zander committed
383 384
  else
    _shellProcess->setWorkingDirectory(QDir::homePath());
385

Robert Knight's avatar
 
Robert Knight committed
386
  _shellProcess->setFlowControlEnabled(_flowControl);
387
  _shellProcess->setErase(_emulation->eraseChar());
388

389
  // this is not strictly accurate use of the COLORFGBG variable.  This does not
390 391 392 393 394
  // 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";

395
  int result = _shellProcess->start(exec,
396
                                  arguments,
397
                                  _environment << backgroundColorHint,
398
                                  windowId(),
399
                                  _addToUtmp,
400
                                  dbusService,
401
                                  (QLatin1String("/Sessions/") +
402
                                   QString::number(_sessionId)));
403

404
  if (result < 0)
405
  {
406
      terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec, arguments.join(" ")));
407
    return;
408
  }
409

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

  emit started();
413 414
}

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

420
    if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) 
Robert Knight's avatar
 
Robert Knight committed
421
    {
422 423 424 425
           if ( _userTitle != caption ) {
            _userTitle = caption;
            modified = true;
        }
426
    }
427

428
    if ((what == IconNameAndWindowTitle) || (what == IconName))
429 430 431 432 433 434
    {
        if ( _iconText != caption ) {
               _iconText = caption;
            modified = true;
        }
    }
435

436
    if (what == TextColor || what == BackgroundColor) 
Robert Knight's avatar
 
Robert Knight committed
437
    {
438
      QString colorString = caption.section(';',0,0);
439 440
      QColor color = QColor(colorString);
      if (color.isValid())
441 442 443 444 445
      {
          if (what == TextColor)
                  emit changeForegroundColorRequest(color);
          else
                  emit changeBackgroundColorRequest(color);
446 447
      }
    }
448

449
    if (what == SessionName) 
Robert Knight's avatar
 
Robert Knight committed
450
    {
451 452 453 454 455
        if ( _nameTitle != caption ) {
               setTitle(Session::NameRole,caption);
            return;
        }
    }
456

Robert Knight's avatar
 
Robert Knight committed
457 458
    if (what == 31) 
    {
459 460 461
       QString cwd=caption;
       cwd=cwd.replace( QRegExp("^~"), QDir::homePath() );
       emit openUrlRequest(cwd);
462
    }
463

Robert Knight's avatar
 
Robert Knight committed
464 465 466
    // change icon via \033]32;Icon\007
    if (what == 32) 
    { 
467 468
        if ( _iconName != caption ) {
               _iconName = caption;
469

470 471
            modified = true;
        }
472
    }
473

474
    if (what == ProfileChange) 
Robert Knight's avatar
 
Robert Knight committed
475
    {
476 477 478 479
        emit profileChangeCommandReceived(caption);
        return;
    }

480 481
    if ( modified )
        emit titleChanged();
482 483
}

484
QString Session::userTitle() const
Robert Knight's avatar
Robert Knight committed
485 486 487
{
    return _userTitle;
}
488 489 490 491 492 493 494 495
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
Robert Knight's avatar
Robert Knight committed
496
{
497 498 499 500 501
    if ( context == LocalTabTitle )
        return _localTabTitleFormat;
    else if ( context == RemoteTabTitle )
        return _remoteTabTitleFormat;

502
    return QString();
Robert Knight's avatar
Robert Knight committed
503 504
}

505
void Session::monitorTimerDone()
506
{
507
  //FIXME: The idea here is that the notification popup will appear to tell the user than output from
508
  //the terminal has stopped and the popup will disappear when the user activates the session.
509 510 511
  //
  //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
512 513
  

514
  //FIXME: Make message text for this notification and the activity notification more descriptive.    
515
  if (_monitorSilence) {
516 517
    KNotification::event("Silence", i18n("Silence in session '%1'", _nameTitle), QPixmap(),
                    QApplication::activeWindow(),
Robert Knight's avatar
Robert Knight committed
518
                    KNotification::CloseWhenWidgetActivated);
519
    emit stateChanged(NOTIFYSILENCE);
520
  }
521 522
  else
  {
523
    emit stateChanged(NOTIFYNORMAL);
524
  }
525

526
  _notifiedActivity=false;
527
}
Robert Knight's avatar
 
Robert Knight committed
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
void Session::updateFlowControlState(bool suspended)
{
    if (suspended)
    {
        if (flowControlEnabled())
        {
            foreach(TerminalDisplay* display,_views)
            {
                if (display->flowControlWarningEnabled())
                    display->outputSuspended(true);
            }
        }
    } 
    else
    {
        foreach(TerminalDisplay* display,_views)
            display->outputSuspended(false);
    }   
}
547
void Session::activityStateSet(int state)
548
{
549
  if (state==NOTIFYBELL)
550
  {
551
      emit bellRequest( i18n("Bell in session '%1'",_nameTitle) );
552 553
  }
  else if (state==NOTIFYACTIVITY)
554
  {
555 556
    if (_monitorSilence) {
      _monitorTimer->start(_silenceSeconds*1000);
557
    }
558 559 560 561 562

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

570
  if ( state==NOTIFYACTIVITY && !_monitorActivity )
571
          state = NOTIFYNORMAL;
572
  if ( state==NOTIFYSILENCE && !_monitorSilence )
573 574
          state = NOTIFYNORMAL;

575
  emit stateChanged(state);
576
}
577

578
void Session::onViewSizeChange(int /*height*/, int /*width*/)
579
{
580
  updateTerminalSize();
581 582
}

583
void Session::updateTerminalSize()
584
{
585
    QListIterator<TerminalDisplay*> viewIter(_views);
586 587 588 589

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

590
    // minimum number of lines and columns that views require for
591 592 593 594 595
    // 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;

596
    //select largest number of lines and columns that will fit in all visible views
597 598
    while ( viewIter.hasNext() )
    {
599
        TerminalDisplay* view = viewIter.next();
600 601
        if ( view->isHidden() == false &&
             view->lines() >= VIEW_LINES_THRESHOLD &&
602
             view->columns() >= VIEW_COLUMNS_THRESHOLD )
603
        {
604 605
            minLines = (minLines == -1) ? view->lines() : qMin( minLines , view->lines() );
            minColumns = (minColumns == -1) ? view->columns() : qMin( minColumns , view->columns() );
606
        }
607
    }
608

609
    // backend emulation must have a _terminal of at least 1 column x 1 line in size
610
    if ( minLines > 0 && minColumns > 0 )
611
    {
612
        _emulation->setImageSize( minLines , minColumns );
613 614
    }
}
615 616 617 618 619
void Session::updateWindowSize(int lines, int columns)
{
    Q_ASSERT(lines > 0 && columns > 0);
    _shellProcess->setWindowSize(lines,columns);
}
620 621 622 623 624 625 626 627 628 629
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
630
    // in the window size (some shells do nothing if the
631 632
    // new and old sizes are the same)
    //
633
    // if there is a more 'correct' way to do this, please
634 635 636 637 638 639 640
    // 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());
}

641
bool Session::kill(int signal)
642
{
643 644 645 646 647 648 649 650 651
    int result = ::kill(_shellProcess->pid(),signal);    
    
    if ( result == 0 )
    {
        _shellProcess->waitForFinished();
        return true;
    }
    else
        return false;
652 653
}

654
void Session::close()
655
{
656 657
  _autoClose = true;
  _wantedClose = true;
658

659
  if (!isRunning() || !kill(SIGHUP))
660
  {
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
     if (isRunning())
     {
        kDebug() << "Process" << _shellProcess->pid() << "did not respond to SIGHUP";

        // close the pty and wait to see if the process finishes.  If it does,
        // the done() slot will have been called so we can return.  Otherwise,
        // emit the finished() signal regardless
        _shellProcess->pty()->close();
        if (_shellProcess->waitForFinished(3000))
            return;

        kWarning() << "Unable to kill process" << _shellProcess->pid();
     }

     // Forced close.
676
     QTimer::singleShot(1, this, SIGNAL(finished()));
677
  }
678 679
}

680
void Session::sendText(const QString &text) const
681
{
Robert Knight's avatar
 
Robert Knight committed
682
  _emulation->sendText(text);
683 684
}

685
Session::~Session()
686
{
Robert Knight's avatar
 
Robert Knight committed
687 688 689 690
  if (_foregroundProcessInfo)
      delete _foregroundProcessInfo;
  if (_sessionProcessInfo)
      delete _sessionProcessInfo;
691 692
  delete _emulation;
  delete _shellProcess;
693
  delete _zmodemProc;
694 695
}

696
void Session::done(int exitStatus)
697
{
698
  if (!_autoClose)
699
  {
700
    _userTitle = i18n("Finished");
701
    emit titleChanged();
702 703
    return;
  }
704

705
  QString message;
706
  if (!_wantedClose || exitStatus != 0)
707
  {
708
    if (_shellProcess->exitStatus() == QProcess::NormalExit)
709
        message = i18n("Program '%1' exited with status %2.", _program, exitStatus);
710
    else
711
        message = i18n("Program '%1' crashed.", _program);
Robert Knight's avatar
Robert Knight committed
712

713
    //FIXME: See comments in Session::monitorTimerDone()
714
    KNotification::event("Finished", message , QPixmap(),
715
                         QApplication::activeWindow(),
716
                         KNotification::CloseWhenWidgetActivated);
717
  }
718

719
  if ( !_wantedClose && _shellProcess->exitStatus() != QProcess::NormalExit )
720
      terminalWarning(message);
721
  else
722
        emit finished();
723 724
}

725
Emulation* Session::emulation() const
726
{
727
  return _emulation;
728 729
}

730
QString Session::keyBindings() const
731
{
732
  return _emulation->keyBindings();
733 734
}

735
QStringList Session::environment() const
736
{
737
  return _environment;
738 739
}

740
void Session::setEnvironment(const QStringList& environment)
741
{
742
    _environment = environment;
743 744
}

745
int Session::sessionId() const
746
{
747
  return _sessionId;
Robert Knight's avatar
 
Robert Knight committed
748
}
749

750
void Session::setKeyBindings(const QString &id)
751
{
752
  _emulation->setKeyBindings(id);
753 754
}