~ubuntu-branches/ubuntu/utopic/kdebase/utopic

« back to all changes in this revision

Viewing changes to konsole/src/Session.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Debian Qt/KDE Maintainers, José Manuel Santamaría Lema, Modestas Vainius
  • Date: 2011-05-26 02:53:50 UTC
  • mfrom: (0.7.7 upstream) (0.4.5 experimental)
  • mto: This revision was merged to the branch mainline in revision 296.
  • Revision ID: james.westby@ubuntu.com-20110526025350-7o10g65yegec2rnq
Tags: 4:4.6.3-1
* New upstream release.

[ José Manuel Santamaría Lema ]
* Bump kde-sc-dev-latest build dependency to 4:4.6.3.
* Bump Standards-Version to 3.9.2; no changes needed.

[ Modestas Vainius ]
* Enable DLRestrictions for libraries in this package. Requires
  libdlrestrictions-dev 0.14 and kdelibs5-dev 4:4.6.3.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
    This file is part of Konsole
 
3
 
 
4
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
 
5
    Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
 
6
    Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de>
 
7
 
 
8
    This program is free software; you can redistribute it and/or modify
 
9
    it under the terms of the GNU General Public License as published by
 
10
    the Free Software Foundation; either version 2 of the License, or
 
11
    (at your option) any later version.
 
12
 
 
13
    This program is distributed in the hope that it will be useful,
 
14
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
    GNU General Public License for more details.
 
17
 
 
18
    You should have received a copy of the GNU General Public License
 
19
    along with this program; if not, write to the Free Software
 
20
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
21
    02110-1301  USA.
 
22
*/
 
23
 
 
24
// Own
 
25
#include "Session.h"
 
26
 
 
27
// Standard
 
28
#include <assert.h>
 
29
#include <stdlib.h>
 
30
#include <signal.h>
 
31
 
 
32
// Qt
 
33
#include <QtGui/QApplication>
 
34
#include <QtCore/QByteRef>
 
35
#include <QtCore/QDir>
 
36
#include <QtCore/QFile>
 
37
#include <QtCore/QRegExp>
 
38
#include <QtCore/QStringList>
 
39
#include <QtDBus/QtDBus>
 
40
#include <QtCore/QDate>
 
41
 
 
42
// KDE
 
43
#include <KDebug>
 
44
#include <KLocale>
 
45
#include <KMessageBox>
 
46
#include <KNotification>
 
47
#include <KProcess>
 
48
#include <KRun>
 
49
#include <kshell.h>
 
50
#include <KStandardDirs>
 
51
#include <KPtyDevice>
 
52
#include <KUrl>
 
53
 
 
54
// Konsole
 
55
#include <config-konsole.h>
 
56
#include <sessionadaptor.h>
 
57
 
 
58
#include "ProcessInfo.h"
 
59
#include "Pty.h"
 
60
#include "TerminalDisplay.h"
 
61
#include "ShellCommand.h"
 
62
#include "Vt102Emulation.h"
 
63
#include "ZModemDialog.h"
 
64
 
 
65
using namespace Konsole;
 
66
 
 
67
int Session::lastSessionId = 0;
 
68
 
 
69
// HACK This is copied out of QUuid::createUuid with reseeding forced.
 
70
// Required because color schemes repeatedly seed the RNG...
 
71
// ...with a constant.
 
72
QUuid createUuid()
 
73
{
 
74
    static const int intbits = sizeof(int)*8;
 
75
    static int randbits = 0;
 
76
    if (!randbits)
 
77
    {
 
78
        int max = RAND_MAX;
 
79
        do { ++randbits; } while ((max=max>>1));
 
80
    }
 
81
 
 
82
    qsrand(uint(QDateTime::currentDateTime().toTime_t()));
 
83
    qrand(); // Skip first
 
84
 
 
85
    QUuid result;
 
86
    uint *data = &(result.data1);
 
87
    int chunks = 16 / sizeof(uint);
 
88
    while (chunks--) {
 
89
        uint randNumber = 0;
 
90
        for (int filled = 0; filled < intbits; filled += randbits)
 
91
            randNumber |= qrand()<<filled;
 
92
         *(data+chunks) = randNumber;
 
93
    }
 
94
 
 
95
    result.data4[0] = (result.data4[0] & 0x3F) | 0x80;        // UV_DCE
 
96
    result.data3 = (result.data3 & 0x0FFF) | 0x4000;        // UV_Random
 
97
 
 
98
    return result;
 
99
}
 
100
 
 
101
Session::Session(QObject* parent) :
 
102
   QObject(parent)
 
103
   , _shellProcess(0)
 
104
   , _emulation(0)
 
105
   , _monitorActivity(false)
 
106
   , _monitorSilence(false)
 
107
   , _notifiedActivity(false)
 
108
   , _autoClose(true)
 
109
   , _wantedClose(false)
 
110
   , _silenceSeconds(10)
 
111
   , _addToUtmp(true)  
 
112
   , _flowControl(true)
 
113
   , _fullScripting(false)
 
114
   , _sessionId(0)
 
115
   , _sessionProcessInfo(0)
 
116
   , _foregroundProcessInfo(0)
 
117
   , _foregroundPid(0)
 
118
   , _zmodemBusy(false)
 
119
   , _zmodemProc(0)
 
120
   , _zmodemProgress(0)
 
121
   , _hasDarkBackground(false)
 
122
{
 
123
    _uniqueIdentifier = createUuid();
 
124
 
 
125
    //prepare DBus communication
 
126
    new SessionAdaptor(this);
 
127
    _sessionId = ++lastSessionId;
 
128
    QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/")+QString::number(_sessionId), this);
 
129
 
 
130
    //create emulation backend
 
131
    _emulation = new Vt102Emulation();
 
132
 
 
133
    connect( _emulation, SIGNAL( titleChanged( int, const QString & ) ),
 
134
           this, SLOT( setUserTitle( int, const QString & ) ) );
 
135
    connect( _emulation, SIGNAL( stateSet(int) ),
 
136
           this, SLOT( activityStateSet(int) ) );
 
137
    connect( _emulation, SIGNAL( zmodemDetected() ), this ,
 
138
            SLOT( fireZModemDetected() ) );
 
139
    connect( _emulation, SIGNAL( changeTabTextColorRequest( int ) ),
 
140
           this, SIGNAL( changeTabTextColorRequest( int ) ) );
 
141
    connect( _emulation, SIGNAL(profileChangeCommandReceived(const QString&)),
 
142
           this, SIGNAL( profileChangeCommandReceived(const QString&)) );
 
143
    connect( _emulation, SIGNAL(flowControlKeyPressed(bool)) , this, 
 
144
             SLOT(updateFlowControlState(bool)) );
 
145
 
 
146
    //create new teletype for I/O with shell process
 
147
    openTeletype(-1);
 
148
 
 
149
    //setup timer for monitoring session activity
 
150
    _monitorTimer = new QTimer(this);
 
151
    _monitorTimer->setSingleShot(true);
 
152
    connect(_monitorTimer, SIGNAL(timeout()), this, SLOT(monitorTimerDone()));
 
153
}
 
154
 
 
155
void Session::openTeletype(int fd)
 
156
{
 
157
    if (_shellProcess && isRunning())
 
158
    {
 
159
        kWarning() << "Attempted to open teletype in a running session.";
 
160
        return;
 
161
    }
 
162
 
 
163
    delete _shellProcess;
 
164
 
 
165
    if (fd < 0)
 
166
        _shellProcess = new Pty();
 
167
    else
 
168
        _shellProcess = new Pty(fd);
 
169
 
 
170
    _shellProcess->setUtf8Mode(_emulation->utf8());
 
171
 
 
172
    //connect teletype to emulation backend
 
173
    connect( _shellProcess,SIGNAL(receivedData(const char*,int)),this,
 
174
            SLOT(onReceiveBlock(const char*,int)) );
 
175
    connect( _emulation,SIGNAL(sendData(const char*,int)),_shellProcess,
 
176
            SLOT(sendData(const char*,int)) );
 
177
    connect( _emulation,SIGNAL(lockPtyRequest(bool)),_shellProcess,SLOT(lockPty(bool)) );
 
178
    connect( _emulation,SIGNAL(useUtf8Request(bool)),_shellProcess,SLOT(setUtf8Mode(bool)) );
 
179
    connect( _shellProcess,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(done(int)) );
 
180
    connect( _emulation,SIGNAL(imageSizeChanged(int,int)),this,SLOT(updateWindowSize(int,int)) );
 
181
}
 
182
 
 
183
WId Session::windowId() const
 
184
{
 
185
    // Returns a window ID for this session which is used
 
186
    // to set the WINDOWID environment variable in the shell
 
187
    // process.
 
188
    //
 
189
    // Sessions can have multiple views or no views, which means
 
190
    // that a single ID is not always going to be accurate.
 
191
    //
 
192
    // If there are no views, the window ID is just 0.  If
 
193
    // there are multiple views, then the window ID for the
 
194
    // top-level window which contains the first view is
 
195
    // returned
 
196
 
 
197
    if ( _views.count() == 0 )
 
198
       return 0;
 
199
    else
 
200
    {
 
201
        QWidget* window = _views.first();
 
202
 
 
203
        Q_ASSERT( window );
 
204
 
 
205
        while ( window->parentWidget() != 0 )
 
206
            window = window->parentWidget();
 
207
 
 
208
        return window->winId();
 
209
    }
 
210
}
 
211
 
 
212
void Session::setDarkBackground(bool darkBackground)
 
213
{
 
214
    _hasDarkBackground = darkBackground;
 
215
}
 
216
bool Session::hasDarkBackground() const
 
217
{
 
218
    return _hasDarkBackground;
 
219
}
 
220
bool Session::isRunning() const
 
221
{
 
222
    return _shellProcess->state() == QProcess::Running;
 
223
}
 
224
 
 
225
void Session::setCodec(QTextCodec* codec)
 
226
{
 
227
    emulation()->setCodec(codec);
 
228
}
 
229
 
 
230
bool Session::setCodec(QByteArray name)
 
231
{
 
232
    QTextCodec *codec = QTextCodec::codecForName(name);
 
233
    if (codec) {
 
234
        setCodec(codec);
 
235
        return true;
 
236
    }
 
237
    return false;
 
238
}
 
239
 
 
240
QByteArray Session::codec()
 
241
{
 
242
    return _emulation->codec()->name();
 
243
}
 
244
 
 
245
void Session::setProgram(const QString& program)
 
246
{
 
247
    _program = ShellCommand::expand(program);
 
248
}
 
249
void Session::setInitialWorkingDirectory(const QString& dir)
 
250
{
 
251
    _initialWorkingDir = KShell::tildeExpand(ShellCommand::expand(dir));
 
252
}
 
253
void Session::setArguments(const QStringList& arguments)
 
254
{
 
255
    _arguments = ShellCommand::expand(arguments);
 
256
}
 
257
 
 
258
QString Session::currentWorkingDirectory()
 
259
{
 
260
    // only returned cached value
 
261
    if (_currentWorkingDir.isEmpty()) updateWorkingDirectory();
 
262
    return _currentWorkingDir;
 
263
}
 
264
ProcessInfo* Session::updateWorkingDirectory()
 
265
{
 
266
    ProcessInfo *process = getProcessInfo();
 
267
    _currentWorkingDir = process->validCurrentDir();
 
268
    return process;
 
269
}
 
270
 
 
271
QList<TerminalDisplay*> Session::views() const
 
272
{
 
273
    return _views;
 
274
}
 
275
 
 
276
void Session::addView(TerminalDisplay* widget)
 
277
{
 
278
     Q_ASSERT( !_views.contains(widget) );
 
279
 
 
280
    _views.append(widget);
 
281
 
 
282
    if ( _emulation != 0 )
 
283
    {
 
284
        // connect emulation - view signals and slots
 
285
        connect( widget , SIGNAL(keyPressedSignal(QKeyEvent*)) , _emulation ,
 
286
               SLOT(sendKeyEvent(QKeyEvent*)) );
 
287
        connect( widget , SIGNAL(mouseSignal(int,int,int,int)) , _emulation ,
 
288
               SLOT(sendMouseEvent(int,int,int,int)) );
 
289
        connect( widget , SIGNAL(sendStringToEmu(const char*)) , _emulation ,
 
290
               SLOT(sendString(const char*)) );
 
291
 
 
292
        // allow emulation to notify view when the foreground process
 
293
        // indicates whether or not it is interested in mouse signals
 
294
        connect( _emulation , SIGNAL(programUsesMouseChanged(bool)) , widget ,
 
295
               SLOT(setUsesMouse(bool)) );
 
296
 
 
297
        widget->setUsesMouse( _emulation->programUsesMouse() );
 
298
 
 
299
        widget->setScreenWindow(_emulation->createWindow());
 
300
    }
 
301
 
 
302
    //connect view signals and slots
 
303
    QObject::connect( widget ,SIGNAL(changedContentSizeSignal(int,int)),this,
 
304
                    SLOT(onViewSizeChange(int,int)));
 
305
 
 
306
    QObject::connect( widget ,SIGNAL(destroyed(QObject*)) , this ,
 
307
                    SLOT(viewDestroyed(QObject*)) );
 
308
}
 
309
 
 
310
void Session::viewDestroyed(QObject* view)
 
311
{
 
312
    TerminalDisplay* display = (TerminalDisplay*)view;
 
313
 
 
314
    Q_ASSERT( _views.contains(display) );
 
315
 
 
316
    removeView(display);
 
317
}
 
318
 
 
319
void Session::removeView(TerminalDisplay* widget)
 
320
{
 
321
    _views.removeAll(widget);
 
322
 
 
323
    disconnect(widget,0,this,0);
 
324
 
 
325
    if ( _emulation != 0 )
 
326
    {
 
327
        // disconnect
 
328
        //  - key presses signals from widget
 
329
        //  - mouse activity signals from widget
 
330
        //  - string sending signals from widget
 
331
        //
 
332
        //  ... and any other signals connected in addView()
 
333
        disconnect( widget, 0, _emulation, 0);
 
334
 
 
335
        // disconnect state change signals emitted by emulation
 
336
        disconnect( _emulation , 0 , widget , 0);
 
337
    }
 
338
 
 
339
    // close the session automatically when the last view is removed
 
340
    if ( _views.count() == 0 )
 
341
    {
 
342
        close();
 
343
    }
 
344
}
 
345
 
 
346
QString Session::checkProgram(const QString& program) const
 
347
{
 
348
  // Upon a KPty error, there is no description on what that error was...
 
349
  // Check to see if the given program is executable.
 
350
  QString exec = QFile::encodeName(program);
 
351
 
 
352
  if (exec.isEmpty())
 
353
      return QString();
 
354
 
 
355
  // if 'exec' is not specified, fall back to default shell.  if that 
 
356
  // is not set then fall back to /bin/sh
 
357
  if ( exec.isEmpty() )
 
358
      exec = qgetenv("SHELL");
 
359
  if ( exec.isEmpty() )
 
360
        exec = "/bin/sh";
 
361
 
 
362
  exec = KRun::binaryName(exec, false);
 
363
  exec = KShell::tildeExpand(exec);
 
364
  QString pexec = KGlobal::dirs()->findExe(exec);
 
365
  if ( pexec.isEmpty() ) 
 
366
  {
 
367
      kError() << i18n("Could not find binary: ") << exec;
 
368
    return QString();
 
369
  }
 
370
 
 
371
  return exec;
 
372
}
 
373
 
 
374
void Session::terminalWarning(const QString& message)
 
375
{
 
376
    static const QByteArray warningText = i18nc("@info:shell Alert the user with red color text", "Warning: ").toLocal8Bit(); 
 
377
    QByteArray messageText = message.toLocal8Bit();
 
378
 
 
379
    static const char redPenOn[] = "\033[1m\033[31m";
 
380
    static const char redPenOff[] = "\033[0m";
 
381
 
 
382
    _emulation->receiveData(redPenOn,strlen(redPenOn));
 
383
    _emulation->receiveData("\n\r\n\r",4);
 
384
    _emulation->receiveData(warningText.constData(),strlen(warningText.constData()));
 
385
    _emulation->receiveData(messageText.constData(),strlen(messageText.constData()));
 
386
    _emulation->receiveData("\n\r\n\r",4);
 
387
    _emulation->receiveData(redPenOff,strlen(redPenOff));
 
388
}
 
389
 
 
390
QString Session::shellSessionId() const
 
391
{
 
392
    QString friendlyUuid(_uniqueIdentifier.toString());
 
393
    friendlyUuid.remove('-').remove('{').remove('}');
 
394
 
 
395
    return friendlyUuid;
 
396
}
 
397
 
 
398
void Session::run()
 
399
{
 
400
  //check that everything is in place to run the session
 
401
  if (_program.isEmpty())
 
402
  {
 
403
      kWarning() << "Session::run() - program to run not set.";
 
404
  }
 
405
  if (_arguments.isEmpty())
 
406
  {
 
407
      kWarning() << "Session::run() - no command line arguments specified.";
 
408
  }
 
409
  if (_uniqueIdentifier.isNull())
 
410
  {
 
411
      _uniqueIdentifier = createUuid();
 
412
  }
 
413
 
 
414
  const int CHOICE_COUNT = 3;
 
415
  QString programs[CHOICE_COUNT] = {_program,qgetenv("SHELL"),"/bin/sh"};
 
416
  QString exec;
 
417
  int choice = 0;
 
418
  while (choice < CHOICE_COUNT)
 
419
  {
 
420
    exec = checkProgram(programs[choice]);
 
421
    if (exec.isEmpty())
 
422
        choice++;
 
423
    else
 
424
        break;
 
425
  }
 
426
 
 
427
  // if a program was specified via setProgram(), but it couldn't be found, print a warning
 
428
  if (choice != 0 && choice < CHOICE_COUNT && !_program.isEmpty())
 
429
  {
 
430
      terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.",_program,exec)); 
 
431
  }
 
432
  // if none of the choices are available, print a warning
 
433
  else if (choice == CHOICE_COUNT)
 
434
  {
 
435
      terminalWarning(i18n("Could not find an interactive shell to start."));
 
436
      return;
 
437
  }
 
438
  
 
439
  // if no arguments are specified, fall back to program name
 
440
  QStringList arguments = _arguments.join(QChar(' ')).isEmpty() ?
 
441
                                                   QStringList() << exec : _arguments;
 
442
 
 
443
  QString dbusService = QDBusConnection::sessionBus().baseService();
 
444
  if (!_initialWorkingDir.isEmpty())
 
445
    _shellProcess->setWorkingDirectory(_initialWorkingDir);
 
446
  else
 
447
    _shellProcess->setWorkingDirectory(QDir::homePath());
 
448
 
 
449
  _shellProcess->setFlowControlEnabled(_flowControl);
 
450
  _shellProcess->setErase(_emulation->eraseChar());
 
451
 
 
452
  // this is not strictly accurate use of the COLORFGBG variable.  This does not
 
453
  // tell the terminal exactly which colors are being used, but instead approximates
 
454
  // the color scheme as "black on white" or "white on black" depending on whether
 
455
  // the background color is deemed dark or not
 
456
  QString backgroundColorHint = _hasDarkBackground ? "COLORFGBG=15;0" : "COLORFGBG=0;15";
 
457
  _environment << backgroundColorHint;
 
458
  _environment << QString("SHELL_SESSION_ID=%1").arg(shellSessionId());
 
459
 
 
460
  int result = _shellProcess->start(exec,
 
461
                                  arguments,
 
462
                                  _environment,
 
463
                                  windowId(),
 
464
                                  _addToUtmp,
 
465
                                  dbusService,
 
466
                                  (QLatin1String("/Sessions/") +
 
467
                                   QString::number(_sessionId)));
 
468
 
 
469
  if (result < 0)
 
470
  {
 
471
      terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec, arguments.join(" ")));
 
472
    return;
 
473
  }
 
474
 
 
475
  _shellProcess->setWriteable(false);  // We are reachable via kwrited.
 
476
 
 
477
  emit started();
 
478
}
 
479
 
 
480
void Session::setUserTitle( int what, const QString &caption )
 
481
{
 
482
    //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle )
 
483
    bool modified = false;
 
484
 
 
485
    if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) 
 
486
    {
 
487
           if ( _userTitle != caption ) {
 
488
            _userTitle = caption;
 
489
            modified = true;
 
490
        }
 
491
    }
 
492
 
 
493
    if ((what == IconNameAndWindowTitle) || (what == IconName))
 
494
    {
 
495
        if ( _iconText != caption ) {
 
496
               _iconText = caption;
 
497
            modified = true;
 
498
        }
 
499
    }
 
500
 
 
501
    if (what == TextColor || what == BackgroundColor) 
 
502
    {
 
503
      QString colorString = caption.section(';',0,0);
 
504
      QColor color = QColor(colorString);
 
505
      if (color.isValid())
 
506
      {
 
507
          if (what == TextColor)
 
508
                  emit changeForegroundColorRequest(color);
 
509
          else
 
510
                  emit changeBackgroundColorRequest(color);
 
511
      }
 
512
    }
 
513
 
 
514
    if (what == SessionName) 
 
515
    {
 
516
        if ( _nameTitle != caption ) {
 
517
               setTitle(Session::NameRole,caption);
 
518
            return;
 
519
        }
 
520
    }
 
521
 
 
522
    if (what == 31) 
 
523
    {
 
524
       QString cwd=caption;
 
525
       cwd=cwd.replace( QRegExp("^~"), QDir::homePath() );
 
526
       emit openUrlRequest(cwd);
 
527
    }
 
528
 
 
529
    // change icon via \033]32;Icon\007
 
530
    if (what == 32) 
 
531
    { 
 
532
        if ( _iconName != caption ) {
 
533
               _iconName = caption;
 
534
 
 
535
            modified = true;
 
536
        }
 
537
    }
 
538
 
 
539
    if (what == ProfileChange) 
 
540
    {
 
541
        emit profileChangeCommandReceived(caption);
 
542
        return;
 
543
    }
 
544
 
 
545
    if ( modified )
 
546
        emit titleChanged();
 
547
}
 
548
 
 
549
QString Session::userTitle() const
 
550
{
 
551
    return _userTitle;
 
552
}
 
553
void Session::setTabTitleFormat(TabTitleContext context , const QString& format)
 
554
{
 
555
    if ( context == LocalTabTitle )
 
556
        _localTabTitleFormat = format;
 
557
    else if ( context == RemoteTabTitle )
 
558
        _remoteTabTitleFormat = format;
 
559
}
 
560
QString Session::tabTitleFormat(TabTitleContext context) const
 
561
{
 
562
    if ( context == LocalTabTitle )
 
563
        return _localTabTitleFormat;
 
564
    else if ( context == RemoteTabTitle )
 
565
        return _remoteTabTitleFormat;
 
566
 
 
567
    return QString();
 
568
}
 
569
 
 
570
void Session::monitorTimerDone()
 
571
{
 
572
  //FIXME: The idea here is that the notification popup will appear to tell the user than output from
 
573
  //the terminal has stopped and the popup will disappear when the user activates the session.
 
574
  //
 
575
  //This breaks with the addition of multiple views of a session.  The popup should disappear
 
576
  //when any of the views of the session becomes active
 
577
  
 
578
 
 
579
  //FIXME: Make message text for this notification and the activity notification more descriptive.    
 
580
  if (_monitorSilence) {
 
581
    KNotification::event("Silence", i18n("Silence in session '%1'", _nameTitle), QPixmap(),
 
582
                    QApplication::activeWindow(),
 
583
                    KNotification::CloseWhenWidgetActivated);
 
584
    emit stateChanged(NOTIFYSILENCE);
 
585
  }
 
586
  else
 
587
  {
 
588
    emit stateChanged(NOTIFYNORMAL);
 
589
  }
 
590
 
 
591
  _notifiedActivity=false;
 
592
}
 
593
void Session::updateFlowControlState(bool suspended)
 
594
{
 
595
    if (suspended)
 
596
    {
 
597
        if (flowControlEnabled())
 
598
        {
 
599
            foreach(TerminalDisplay* display,_views)
 
600
            {
 
601
                if (display->flowControlWarningEnabled())
 
602
                    display->outputSuspended(true);
 
603
            }
 
604
        }
 
605
    } 
 
606
    else
 
607
    {
 
608
        foreach(TerminalDisplay* display,_views)
 
609
            display->outputSuspended(false);
 
610
    }   
 
611
}
 
612
void Session::activityStateSet(int state)
 
613
{
 
614
  if (state==NOTIFYBELL)
 
615
  {
 
616
      emit bellRequest( i18n("Bell in session '%1'",_nameTitle) );
 
617
  }
 
618
  else if (state==NOTIFYACTIVITY)
 
619
  {
 
620
    if (_monitorSilence) {
 
621
      _monitorTimer->start(_silenceSeconds*1000);
 
622
    }
 
623
 
 
624
    if ( _monitorActivity ) {
 
625
      //FIXME:  See comments in Session::monitorTimerDone()
 
626
      if (!_notifiedActivity) {
 
627
        KNotification::event("Activity", i18n("Activity in session '%1'", _nameTitle), QPixmap(),
 
628
                        QApplication::activeWindow(),
 
629
        KNotification::CloseWhenWidgetActivated);
 
630
        _notifiedActivity=true;
 
631
      }
 
632
    }
 
633
  }
 
634
 
 
635
  if ( state==NOTIFYACTIVITY && !_monitorActivity )
 
636
          state = NOTIFYNORMAL;
 
637
  if ( state==NOTIFYSILENCE && !_monitorSilence )
 
638
          state = NOTIFYNORMAL;
 
639
 
 
640
  emit stateChanged(state);
 
641
}
 
642
 
 
643
void Session::onViewSizeChange(int /*height*/, int /*width*/)
 
644
{
 
645
  updateTerminalSize();
 
646
}
 
647
 
 
648
void Session::updateTerminalSize()
 
649
{
 
650
    QListIterator<TerminalDisplay*> viewIter(_views);
 
651
 
 
652
    int minLines = -1;
 
653
    int minColumns = -1;
 
654
 
 
655
    // minimum number of lines and columns that views require for
 
656
    // their size to be taken into consideration ( to avoid problems
 
657
    // with new view widgets which haven't yet been set to their correct size )
 
658
    const int VIEW_LINES_THRESHOLD = 2;
 
659
    const int VIEW_COLUMNS_THRESHOLD = 2;
 
660
 
 
661
    //select largest number of lines and columns that will fit in all visible views
 
662
    while ( viewIter.hasNext() )
 
663
    {
 
664
        TerminalDisplay* view = viewIter.next();
 
665
        if ( view->isHidden() == false &&
 
666
             view->lines() >= VIEW_LINES_THRESHOLD &&
 
667
             view->columns() >= VIEW_COLUMNS_THRESHOLD )
 
668
        {
 
669
            minLines = (minLines == -1) ? view->lines() : qMin( minLines , view->lines() );
 
670
            minColumns = (minColumns == -1) ? view->columns() : qMin( minColumns , view->columns() );
 
671
            view->processFilters();
 
672
        }
 
673
    }
 
674
 
 
675
    // backend emulation must have a _terminal of at least 1 column x 1 line in size
 
676
    if ( minLines > 0 && minColumns > 0 )
 
677
    {
 
678
        _emulation->setImageSize( minLines , minColumns );
 
679
    }
 
680
}
 
681
void Session::updateWindowSize(int lines, int columns)
 
682
{
 
683
    Q_ASSERT(lines > 0 && columns > 0);
 
684
    _shellProcess->setWindowSize(lines,columns);
 
685
}
 
686
void Session::refresh()
 
687
{
 
688
    // attempt to get the shell process to redraw the display
 
689
    //
 
690
    // this requires the program running in the shell
 
691
    // to cooperate by sending an update in response to
 
692
    // a window size change
 
693
    //
 
694
    // the window size is changed twice, first made slightly larger and then
 
695
    // resized back to its normal size so that there is actually a change
 
696
    // in the window size (some shells do nothing if the
 
697
    // new and old sizes are the same)
 
698
    //
 
699
    // if there is a more 'correct' way to do this, please
 
700
    // send an email with method or patches to konsole-devel@kde.org
 
701
 
 
702
    const QSize existingSize = _shellProcess->windowSize();
 
703
    _shellProcess->setWindowSize(existingSize.height(),existingSize.width()+1);
 
704
    _shellProcess->setWindowSize(existingSize.height(),existingSize.width());
 
705
}
 
706
 
 
707
bool Session::kill(int signal)
 
708
{
 
709
    int result = ::kill(_shellProcess->pid(),signal);    
 
710
    
 
711
    if ( result == 0 )
 
712
    {
 
713
        _shellProcess->waitForFinished();
 
714
        return true;
 
715
    }
 
716
    else
 
717
        return false;
 
718
}
 
719
 
 
720
void Session::close()
 
721
{
 
722
  _autoClose = true;
 
723
  _wantedClose = true;
 
724
 
 
725
  if (!isRunning() || !kill(SIGHUP))
 
726
  {
 
727
     if (isRunning())
 
728
     {
 
729
        kWarning() << "Process" << _shellProcess->pid() << "did not respond to SIGHUP";
 
730
 
 
731
        // close the pty and wait to see if the process finishes.  If it does,
 
732
        // the done() slot will have been called so we can return.  Otherwise,
 
733
        // emit the finished() signal regardless
 
734
        _shellProcess->pty()->close();
 
735
        if (_shellProcess->waitForFinished(3000))
 
736
            return;
 
737
 
 
738
        kWarning() << "Unable to kill process" << _shellProcess->pid();
 
739
     }
 
740
 
 
741
     // Forced close.
 
742
     QTimer::singleShot(1, this, SIGNAL(finished()));
 
743
  }
 
744
}
 
745
 
 
746
void Session::sendText(const QString &text) const
 
747
{
 
748
  _emulation->sendText(text);
 
749
}
 
750
 
 
751
void Session::sendMouseEvent(int buttons, int column, int line, int eventType)
 
752
{
 
753
    _emulation->sendMouseEvent(buttons, column, line, eventType);
 
754
}
 
755
 
 
756
Session::~Session()
 
757
{
 
758
  if (_foregroundProcessInfo)
 
759
      delete _foregroundProcessInfo;
 
760
  if (_sessionProcessInfo)
 
761
      delete _sessionProcessInfo;
 
762
  delete _emulation;
 
763
  delete _shellProcess;
 
764
  delete _zmodemProc;
 
765
}
 
766
 
 
767
void Session::done(int exitStatus)
 
768
{
 
769
  if (!_autoClose)
 
770
  {
 
771
    _userTitle = i18nc("@info:shell This session is done", "Finished");
 
772
    emit titleChanged();
 
773
    return;
 
774
  }
 
775
 
 
776
  QString message;
 
777
  if (!_wantedClose || exitStatus != 0)
 
778
  {
 
779
    if (_shellProcess->exitStatus() == QProcess::NormalExit)
 
780
        message = i18n("Program '%1' exited with status %2.", _program, exitStatus);
 
781
    else
 
782
        message = i18n("Program '%1' crashed.", _program);
 
783
 
 
784
    //FIXME: See comments in Session::monitorTimerDone()
 
785
    KNotification::event("Finished", message , QPixmap(),
 
786
                         QApplication::activeWindow(),
 
787
                         KNotification::CloseWhenWidgetActivated);
 
788
  }
 
789
 
 
790
  if ( !_wantedClose && _shellProcess->exitStatus() != QProcess::NormalExit )
 
791
      terminalWarning(message);
 
792
  else
 
793
        emit finished();
 
794
}
 
795
 
 
796
Emulation* Session::emulation() const
 
797
{
 
798
  return _emulation;
 
799
}
 
800
 
 
801
QString Session::keyBindings() const
 
802
{
 
803
  return _emulation->keyBindings();
 
804
}
 
805
 
 
806
QStringList Session::environment() const
 
807
{
 
808
  return _environment;
 
809
}
 
810
 
 
811
void Session::setEnvironment(const QStringList& environment)
 
812
{
 
813
    _environment = environment;
 
814
}
 
815
 
 
816
int Session::sessionId() const
 
817
{
 
818
  return _sessionId;
 
819
}
 
820
 
 
821
void Session::setKeyBindings(const QString &id)
 
822
{
 
823
  _emulation->setKeyBindings(id);
 
824
}
 
825
 
 
826
void Session::setTitle(TitleRole role , const QString& newTitle)
 
827
{
 
828
    if ( title(role) != newTitle )
 
829
    {
 
830
        if ( role == NameRole )
 
831
            _nameTitle = newTitle;
 
832
        else if ( role == DisplayedTitleRole )
 
833
            _displayTitle = newTitle;
 
834
 
 
835
        emit titleChanged();
 
836
    }
 
837
}
 
838
 
 
839
QString Session::title(TitleRole role) const
 
840
{
 
841
    if ( role == NameRole )
 
842
        return _nameTitle;
 
843
    else if ( role == DisplayedTitleRole )
 
844
        return _displayTitle;
 
845
    else
 
846
        return QString();
 
847
}
 
848
 
 
849
ProcessInfo* Session::getProcessInfo()
 
850
{
 
851
    ProcessInfo* process;
 
852
 
 
853
    if (isForegroundProcessActive())
 
854
        process = _foregroundProcessInfo;
 
855
    else
 
856
    {
 
857
        updateSessionProcessInfo();
 
858
        process = _sessionProcessInfo;
 
859
    }
 
860
 
 
861
    return process;
 
862
}
 
863
 
 
864
void Session::updateSessionProcessInfo()
 
865
{
 
866
    Q_ASSERT(_shellProcess);
 
867
    if (!_sessionProcessInfo)
 
868
    {
 
869
        _sessionProcessInfo = ProcessInfo::newInstance(processId());
 
870
        _sessionProcessInfo->setUserHomeDir();
 
871
    }
 
872
    _sessionProcessInfo->update();
 
873
}
 
874
 
 
875
bool Session::updateForegroundProcessInfo()
 
876
{
 
877
    bool valid = (_foregroundProcessInfo != 0);
 
878
 
 
879
    // has foreground process changed?
 
880
    Q_ASSERT(_shellProcess);
 
881
    int pid = _shellProcess->foregroundProcessGroup();
 
882
    if (pid != _foregroundPid)
 
883
    {
 
884
        if (valid)
 
885
            delete _foregroundProcessInfo;
 
886
        _foregroundProcessInfo = ProcessInfo::newInstance(pid);
 
887
        _foregroundPid = pid;
 
888
        valid = true;
 
889
    }
 
890
 
 
891
    if (valid)
 
892
    {
 
893
        _foregroundProcessInfo->update();
 
894
        valid = _foregroundProcessInfo->isValid();
 
895
    }
 
896
 
 
897
    return valid;
 
898
}
 
899
 
 
900
bool Session::isRemote()
 
901
{
 
902
    ProcessInfo* process = getProcessInfo();
 
903
 
 
904
    bool ok = false;
 
905
    return ( process->name(&ok) == "ssh" && ok );
 
906
}
 
907
 
 
908
QString Session::getDynamicTitle()
 
909
{
 
910
    // update current directory from process
 
911
    ProcessInfo* process = updateWorkingDirectory();
 
912
 
 
913
    // format tab titles using process info
 
914
    bool ok = false;
 
915
    QString title;
 
916
    if ( process->name(&ok) == "ssh" && ok )
 
917
    {
 
918
        SSHProcessInfo sshInfo(*process);
 
919
        title = sshInfo.format(tabTitleFormat(Session::RemoteTabTitle));
 
920
    }
 
921
    else
 
922
        title = process->format(tabTitleFormat(Session::LocalTabTitle));
 
923
 
 
924
    return title;
 
925
}
 
926
 
 
927
KUrl Session::getUrl()
 
928
{
 
929
    QString path;
 
930
    
 
931
    updateSessionProcessInfo();
 
932
    if (_sessionProcessInfo->isValid())
 
933
    {
 
934
        bool ok = false;
 
935
 
 
936
        // check if foreground process is bookmark-able
 
937
        if (isForegroundProcessActive())
 
938
        {
 
939
            // for remote connections, save the user and host
 
940
            // bright ideas to get the directory at the other end are welcome :)
 
941
            if (_foregroundProcessInfo->name(&ok) == "ssh" && ok)
 
942
            {
 
943
                SSHProcessInfo sshInfo(*_foregroundProcessInfo);
 
944
                path = "ssh://" + sshInfo.userName() + '@' + sshInfo.host();
 
945
            }
 
946
            else
 
947
            {
 
948
                path = _foregroundProcessInfo->currentDir(&ok);
 
949
                if (!ok)
 
950
                    path.clear();
 
951
            }
 
952
        }
 
953
        else // otherwise use the current working directory of the shell process
 
954
        {
 
955
            path = _sessionProcessInfo->currentDir(&ok);
 
956
            if (!ok)
 
957
                path.clear();
 
958
        }
 
959
    }
 
960
 
 
961
    return KUrl(path);
 
962
}
 
963
 
 
964
void Session::setIconName(const QString& iconName)
 
965
{
 
966
    if ( iconName != _iconName )
 
967
    {
 
968
        _iconName = iconName;
 
969
        emit titleChanged();
 
970
    }
 
971
}
 
972
 
 
973
void Session::setIconText(const QString& iconText)
 
974
{
 
975
  _iconText = iconText;
 
976
}
 
977
 
 
978
QString Session::iconName() const
 
979
{
 
980
  return _iconName;
 
981
}
 
982
 
 
983
QString Session::iconText() const
 
984
{
 
985
  return _iconText;
 
986
}
 
987
 
 
988
void Session::setHistoryType(const HistoryType &hType)
 
989
{
 
990
  _emulation->setHistory(hType);
 
991
}
 
992
 
 
993
const HistoryType& Session::historyType() const
 
994
{
 
995
  return _emulation->history();
 
996
}
 
997
 
 
998
void Session::clearHistory()
 
999
{
 
1000
    _emulation->clearHistory();
 
1001
}
 
1002
 
 
1003
QStringList Session::arguments() const
 
1004
{
 
1005
  return _arguments;
 
1006
}
 
1007
 
 
1008
QString Session::program() const
 
1009
{
 
1010
  return _program;
 
1011
}
 
1012
 
 
1013
// unused currently
 
1014
bool Session::isMonitorActivity() const { return _monitorActivity; }
 
1015
// unused currently
 
1016
bool Session::isMonitorSilence()  const { return _monitorSilence; }
 
1017
 
 
1018
void Session::setMonitorActivity(bool _monitor)
 
1019
{
 
1020
  _monitorActivity=_monitor;
 
1021
  _notifiedActivity=false;
 
1022
 
 
1023
  activityStateSet(NOTIFYNORMAL);
 
1024
}
 
1025
 
 
1026
void Session::setMonitorSilence(bool _monitor)
 
1027
{
 
1028
  if (_monitorSilence==_monitor)
 
1029
    return;
 
1030
 
 
1031
  _monitorSilence=_monitor;
 
1032
  if (_monitorSilence)
 
1033
  {
 
1034
    _monitorTimer->start(_silenceSeconds*1000);
 
1035
  }
 
1036
  else
 
1037
    _monitorTimer->stop();
 
1038
 
 
1039
  activityStateSet(NOTIFYNORMAL);
 
1040
}
 
1041
 
 
1042
void Session::setMonitorSilenceSeconds(int seconds)
 
1043
{
 
1044
  _silenceSeconds=seconds;
 
1045
  if (_monitorSilence) {
 
1046
    _monitorTimer->start(_silenceSeconds*1000);
 
1047
  }
 
1048
}
 
1049
 
 
1050
void Session::setAddToUtmp(bool set)
 
1051
{
 
1052
  _addToUtmp = set;
 
1053
}
 
1054
 
 
1055
void Session::setFlowControlEnabled(bool enabled)
 
1056
{
 
1057
  _flowControl = enabled;
 
1058
 
 
1059
  if (_shellProcess)  
 
1060
    _shellProcess->setFlowControlEnabled(_flowControl);
 
1061
  emit flowControlEnabledChanged(enabled);
 
1062
}
 
1063
bool Session::flowControlEnabled() const
 
1064
{
 
1065
    if (_shellProcess)
 
1066
            return _shellProcess->flowControlEnabled();
 
1067
    else
 
1068
            return _flowControl;
 
1069
}
 
1070
void Session::fireZModemDetected()
 
1071
{
 
1072
  if (!_zmodemBusy)
 
1073
  {
 
1074
    QTimer::singleShot(10, this, SIGNAL(zmodemDetected()));
 
1075
    _zmodemBusy = true;
 
1076
  }
 
1077
}
 
1078
 
 
1079
void Session::cancelZModem()
 
1080
{
 
1081
  _shellProcess->sendData("\030\030\030\030", 4); // Abort
 
1082
  _zmodemBusy = false;
 
1083
}
 
1084
 
 
1085
void Session::startZModem(const QString &zmodem, const QString &dir, const QStringList &list)
 
1086
{
 
1087
  _zmodemBusy = true;
 
1088
  _zmodemProc = new KProcess();
 
1089
  _zmodemProc->setOutputChannelMode( KProcess::SeparateChannels );
 
1090
 
 
1091
  *_zmodemProc << zmodem << "-v" << list;
 
1092
 
 
1093
  if (!dir.isEmpty())
 
1094
     _zmodemProc->setWorkingDirectory(dir);
 
1095
 
 
1096
  connect(_zmodemProc,SIGNAL (readyReadStandardOutput()),
 
1097
          this, SLOT(zmodemReadAndSendBlock()));
 
1098
  connect(_zmodemProc,SIGNAL (readyReadStandardError()),
 
1099
          this, SLOT(zmodemReadStatus()));
 
1100
  connect(_zmodemProc,SIGNAL (finished(int,QProcess::ExitStatus)),
 
1101
          this, SLOT(zmodemFinished()));
 
1102
 
 
1103
  _zmodemProc->start();
 
1104
  
 
1105
  disconnect( _shellProcess,SIGNAL(receivedData(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) );
 
1106
  connect( _shellProcess,SIGNAL(receivedData(const char*,int)), this, SLOT(zmodemRcvBlock(const char*,int)) );
 
1107
 
 
1108
  _zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false,
 
1109
                                    i18n("ZModem Progress"));
 
1110
 
 
1111
  connect(_zmodemProgress, SIGNAL(user1Clicked()),
 
1112
          this, SLOT(zmodemFinished()));
 
1113
 
 
1114
  _zmodemProgress->show();
 
1115
}
 
1116
 
 
1117
void Session::zmodemReadAndSendBlock()
 
1118
{
 
1119
  _zmodemProc->setReadChannel( QProcess::StandardOutput );
 
1120
  QByteArray data = _zmodemProc->readAll();
 
1121
 
 
1122
  if ( data.count() == 0 )
 
1123
      return;
 
1124
 
 
1125
  _shellProcess->sendData(data.constData(),data.count());
 
1126
}
 
1127
 
 
1128
void Session::zmodemReadStatus()
 
1129
{
 
1130
  _zmodemProc->setReadChannel( QProcess::StandardError );
 
1131
  QByteArray msg = _zmodemProc->readAll();
 
1132
  while(!msg.isEmpty())
 
1133
  {
 
1134
     int i = msg.indexOf('\015');
 
1135
     int j = msg.indexOf('\012');
 
1136
     QByteArray txt;
 
1137
     if ((i != -1) && ((j == -1) || (i < j)))
 
1138
     {
 
1139
       msg = msg.mid(i+1);
 
1140
     }
 
1141
     else if (j != -1)
 
1142
     {
 
1143
       txt = msg.left(j);
 
1144
       msg = msg.mid(j+1);
 
1145
     }
 
1146
     else
 
1147
     {
 
1148
       txt = msg;
 
1149
       msg.truncate(0);
 
1150
     }
 
1151
     if (!txt.isEmpty())
 
1152
       _zmodemProgress->addProgressText(QString::fromLocal8Bit(txt));
 
1153
  }
 
1154
}
 
1155
 
 
1156
void Session::zmodemRcvBlock(const char *data, int len)
 
1157
{
 
1158
  QByteArray ba( data, len );
 
1159
 
 
1160
  _zmodemProc->write( ba );
 
1161
}
 
1162
 
 
1163
void Session::zmodemFinished()
 
1164
{
 
1165
  /* zmodemFinished() is called by QProcess's finished() and
 
1166
      ZModemDialog's user1Clicked(). Therefore, an invocation by
 
1167
      user1Clicked() will recursively invoke this function again
 
1168
      when the KProcess is deleted! */
 
1169
  if (_zmodemProc) {
 
1170
    KProcess* process = _zmodemProc;
 
1171
    _zmodemProc = 0;   // Set _zmodemProc to 0 avoid recursive invocations!
 
1172
    _zmodemBusy = false;
 
1173
    delete process;    // Now, the KProcess may be disposed safely.
 
1174
 
 
1175
    disconnect( _shellProcess,SIGNAL(receivedData(const char*,int)), this ,SLOT(zmodemRcvBlock(const char*,int)) );
 
1176
    connect( _shellProcess,SIGNAL(receivedData(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) );
 
1177
 
 
1178
    _shellProcess->sendData("\030\030\030\030", 4); // Abort
 
1179
    _shellProcess->sendData("\001\013\n", 3); // Try to get prompt back
 
1180
    _zmodemProgress->transferDone();
 
1181
  }
 
1182
}
 
1183
 
 
1184
void Session::onReceiveBlock( const char* buf, int len )
 
1185
{
 
1186
    _emulation->receiveData( buf, len );
 
1187
    emit receivedData( QString::fromLatin1( buf, len ) );
 
1188
}
 
1189
 
 
1190
QSize Session::size()
 
1191
{
 
1192
  return _emulation->imageSize();
 
1193
}
 
1194
 
 
1195
void Session::setSize(const QSize& size)
 
1196
{
 
1197
  if ((size.width() <= 1) || (size.height() <= 1))
 
1198
     return;
 
1199
 
 
1200
  emit resizeRequest(size);
 
1201
}
 
1202
int Session::processId() const
 
1203
{
 
1204
    return _shellProcess->pid();
 
1205
}
 
1206
 
 
1207
void Session::setTitle(int role , const QString& title)
 
1208
{
 
1209
    switch (role) {
 
1210
    case (0):
 
1211
        this->setTitle(Session::NameRole, title);
 
1212
        break;
 
1213
    case (1):
 
1214
        this->setTitle(Session::DisplayedTitleRole, title);
 
1215
        break;
 
1216
    }
 
1217
}
 
1218
 
 
1219
QString Session::title(int role) const
 
1220
{
 
1221
    switch (role) {
 
1222
    case (0):
 
1223
        return this->title(Session::NameRole);
 
1224
    case (1):
 
1225
        return this->title(Session::DisplayedTitleRole);
 
1226
    default:
 
1227
        return QString();
 
1228
    }
 
1229
}
 
1230
 
 
1231
void Session::setTabTitleFormat(int context , const QString& format)
 
1232
{
 
1233
    switch (context) {
 
1234
    case (0):
 
1235
        this->setTabTitleFormat(Session::LocalTabTitle, format);
 
1236
        break;
 
1237
    case (1):
 
1238
        this->setTabTitleFormat(Session::RemoteTabTitle, format);
 
1239
        break;
 
1240
    }
 
1241
}
 
1242
 
 
1243
QString Session::tabTitleFormat(int context) const
 
1244
{
 
1245
    switch (context) {
 
1246
    case (0):
 
1247
        return this->tabTitleFormat(Session::LocalTabTitle);
 
1248
    case (1):
 
1249
        return this->tabTitleFormat(Session::RemoteTabTitle);
 
1250
    default:
 
1251
        return QString();
 
1252
    }
 
1253
}
 
1254
 
 
1255
int Session::foregroundProcessId()
 
1256
{
 
1257
    int pid;
 
1258
 
 
1259
    bool ok = false;
 
1260
    pid = getProcessInfo()->pid(&ok);
 
1261
    if (!ok)
 
1262
        pid = -1;
 
1263
 
 
1264
    return pid;
 
1265
}
 
1266
 
 
1267
bool Session::isForegroundProcessActive()
 
1268
{
 
1269
    // foreground process info is always updated after this
 
1270
    return updateForegroundProcessInfo() && (processId() != _foregroundPid);
 
1271
}
 
1272
 
 
1273
QString Session::foregroundProcessName()
 
1274
{
 
1275
    QString name;
 
1276
 
 
1277
    if (updateForegroundProcessInfo()) 
 
1278
    {
 
1279
        bool ok = false;
 
1280
        name = _foregroundProcessInfo->name(&ok);
 
1281
        if (!ok)
 
1282
            name.clear();
 
1283
    }
 
1284
 
 
1285
    return name;
 
1286
}
 
1287
 
 
1288
void Session::saveSession(KConfigGroup& group)
 
1289
{
 
1290
    group.writePathEntry("WorkingDir", currentWorkingDirectory());
 
1291
    group.writeEntry("LocalTab",       tabTitleFormat(LocalTabTitle));
 
1292
    group.writeEntry("RemoteTab",      tabTitleFormat(RemoteTabTitle));
 
1293
    group.writeEntry("SessionGuid",    _uniqueIdentifier.toString());
 
1294
    group.writeEntry("Encoding",       QString(codec()));
 
1295
}
 
1296
 
 
1297
void Session::restoreSession(KConfigGroup& group)
 
1298
{
 
1299
    QString value;
 
1300
 
 
1301
    value = group.readPathEntry("WorkingDir", QString());
 
1302
    if (!value.isEmpty()) setInitialWorkingDirectory(value);
 
1303
    value = group.readEntry("LocalTab");
 
1304
    if (!value.isEmpty()) setTabTitleFormat(LocalTabTitle, value);
 
1305
    value = group.readEntry("RemoteTab");
 
1306
    if (!value.isEmpty()) setTabTitleFormat(RemoteTabTitle, value);
 
1307
    value = group.readEntry("SessionGuid");
 
1308
    if (!value.isEmpty()) _uniqueIdentifier = QUuid(value);
 
1309
    value = group.readEntry("Encoding");
 
1310
    if (!value.isEmpty()) setCodec(value.toUtf8());
 
1311
}
 
1312
 
 
1313
SessionGroup::SessionGroup(QObject* parent)
 
1314
    : QObject(parent), _masterMode(0)
 
1315
{
 
1316
}
 
1317
SessionGroup::~SessionGroup()
 
1318
{
 
1319
}
 
1320
int SessionGroup::masterMode() const { return _masterMode; }
 
1321
QList<Session*> SessionGroup::sessions() const { return _sessions.keys(); }
 
1322
bool SessionGroup::masterStatus(Session* session) const { return _sessions[session]; }
 
1323
 
 
1324
void SessionGroup::addSession(Session* session)
 
1325
{
 
1326
    connect(session,SIGNAL(finished()),this,SLOT(sessionFinished()));
 
1327
    _sessions.insert(session,false);
 
1328
}
 
1329
void SessionGroup::removeSession(Session* session)
 
1330
{
 
1331
    disconnect(session,SIGNAL(finished()),this,SLOT(sessionFinished()));
 
1332
    setMasterStatus(session,false);
 
1333
    _sessions.remove(session);
 
1334
}
 
1335
void SessionGroup::sessionFinished()
 
1336
{
 
1337
    Session* session = qobject_cast<Session*>(sender());
 
1338
    Q_ASSERT(session);
 
1339
    removeSession(session);
 
1340
}
 
1341
void SessionGroup::setMasterMode(int mode)
 
1342
{
 
1343
   _masterMode = mode;
 
1344
}
 
1345
QList<Session*> SessionGroup::masters() const
 
1346
{
 
1347
    return _sessions.keys(true);
 
1348
}
 
1349
void SessionGroup::setMasterStatus(Session* session , bool master)
 
1350
{
 
1351
    const bool wasMaster = _sessions[session];
 
1352
 
 
1353
    if (wasMaster == master) {
 
1354
        // No status change -> nothing to do.
 
1355
        return;
 
1356
    }
 
1357
    _sessions[session] = master;
 
1358
 
 
1359
    if(master) {
 
1360
        connect( session->emulation() , SIGNAL(sendData(const char*,int)) , this,
 
1361
                 SLOT(forwardData(const char*,int)) );
 
1362
    }
 
1363
    else {
 
1364
        disconnect( session->emulation() , SIGNAL(sendData(const char*,int)) , this,
 
1365
                    SLOT(forwardData(const char*,int)) );
 
1366
    }
 
1367
}
 
1368
void SessionGroup::forwardData(const char* data, int size)
 
1369
{
 
1370
    static bool _inForwardData = false;
 
1371
    if(_inForwardData) {   // Avoid recursive calls among session groups!
 
1372
       // A recursive call happens when a master in group A calls forwardData()
 
1373
       // in group B. If one of the destination sessions in group B is also a
 
1374
       // master of a group including the master session of group A, this would
 
1375
       // again call forwardData() in group A, and so on.
 
1376
       return;
 
1377
    }
 
1378
    
 
1379
    _inForwardData = true;
 
1380
    QListIterator<Session*> iter(_sessions.keys());
 
1381
    while(iter.hasNext()) {
 
1382
        Session* other = iter.next();
 
1383
        if(!_sessions[other]) {
 
1384
           other->emulation()->sendString(data, size);
 
1385
        }
 
1386
    }
 
1387
    _inForwardData = false;
 
1388
}
 
1389
 
 
1390
#include "Session.moc"
 
1391