Session.cpp 39.6 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
    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
11
12
13
14
15
16
17
18
19
20
21
22
    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.
*/
23

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

27
// Standard
28
#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
46
#include <KDebug>
#include <KLocale>
#include <KMessageBox>
#include <KNotification>
#include <KRun>
47
#include <KShell>
48
#include <KProcess>
49
#include <KStandardDirs>
50
#include <KPtyDevice>
51
#include <KConfigGroup>
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
#include "History.h"
64

65
using namespace Konsole;
66

67
68
int Session::lastSessionId = 0;

69
70
71
72
73
// HACK This is copied out of QUuid::createUuid with reseeding forced.
// Required because color schemes repeatedly seed the RNG...
// ...with a constant.
QUuid createUuid()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
74
    static const int intbits = sizeof(int) * 8;
75
    static int randbits = 0;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
76
    if (!randbits) {
77
        int max = RAND_MAX;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
78
79
80
        do {
            ++randbits;
        } while ((max = max >> 1));
81
82
83
84
85
86
    }

    qsrand(uint(QDateTime::currentDateTime().toTime_t()));
    qrand(); // Skip first

    QUuid result;
87
    uint* data = &(result.data1);
88
89
90
91
    int chunks = 16 / sizeof(uint);
    while (chunks--) {
        uint randNumber = 0;
        for (int filled = 0; filled < intbits; filled += randbits)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
92
93
            randNumber |= qrand() << filled;
        *(data + chunks) = randNumber;
94
95
96
97
98
99
100
101
    }

    result.data4[0] = (result.data4[0] & 0x3F) | 0x80;        // UV_DCE
    result.data3 = (result.data3 & 0x0FFF) | 0x4000;        // UV_Random

    return result;
}

102
Session::Session(QObject* parent) :
Kurt Hindenburg's avatar
Kurt Hindenburg committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
    QObject(parent)
    , _shellProcess(0)
    , _emulation(0)
    , _monitorActivity(false)
    , _monitorSilence(false)
    , _notifiedActivity(false)
    , _silenceSeconds(10)
    , _autoClose(true)
    , _closePerUserRequest(false)
    , _addToUtmp(true)
    , _flowControl(true)
    , _sessionId(0)
    , _sessionProcessInfo(0)
    , _foregroundProcessInfo(0)
    , _foregroundPid(0)
    , _zmodemBusy(false)
    , _zmodemProc(0)
    , _zmodemProgress(0)
    , _hasDarkBackground(false)
Robert Knight's avatar
   
Robert Knight committed
122
{
123
124
    _uniqueIdentifier = createUuid();

Robert Knight's avatar
   
Robert Knight committed
125
    //prepare DBus communication
126
    new SessionAdaptor(this);
127
    _sessionId = ++lastSessionId;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
128
    QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/") + QString::number(_sessionId), this);
Robert Knight's avatar
   
Robert Knight committed
129
130

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

133
134
    connect(_emulation, SIGNAL(titleChanged(int,QString)),
            this, SLOT(setUserTitle(int,QString)));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
135
136
    connect(_emulation, SIGNAL(stateSet(int)),
            this, SLOT(activityStateSet(int)));
137
    connect(_emulation, SIGNAL(zmodemDetected()), this,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
138
139
140
141
142
            SLOT(fireZModemDetected()));
    connect(_emulation, SIGNAL(changeTabTextColorRequest(int)),
            this, SIGNAL(changeTabTextColorRequest(int)));
    connect(_emulation, SIGNAL(profileChangeCommandReceived(QString)),
            this, SIGNAL(profileChangeCommandReceived(QString)));
143
    connect(_emulation, SIGNAL(flowControlKeyPressed(bool)), this,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
144
            SLOT(updateFlowControlState(bool)));
145
    connect(_emulation, SIGNAL(primaryScreenInUse(bool)), this,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
146
            SLOT(onPrimaryScreenInUse(bool)));
147
    connect(_emulation, SIGNAL(selectedText(QString)), this,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
148
            SLOT(onSelectedText(QString)));
Robert Knight's avatar
   
Robert Knight committed
149

150
151
    //create new teletype for I/O with shell process
    openTeletype(-1);
152

153
    //setup timer for monitoring session activity
154
155
156
    _silenceTimer = new QTimer(this);
    _silenceTimer->setSingleShot(true);
    connect(_silenceTimer, SIGNAL(timeout()), this, SLOT(silenceTimerDone()));
157

Kurt Hindenburg's avatar
Kurt Hindenburg committed
158
    _activityTimer = new QTimer(this);
159
160
    _activityTimer->setSingleShot(true);
    connect(_activityTimer, SIGNAL(timeout()), this, SLOT(activityTimerDone()));
161
162
163
164
}

void Session::openTeletype(int fd)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
165
    if (isRunning()) {
166
167
168
        kWarning() << "Attempted to open teletype in a running session.";
        return;
    }
169

170
    delete _shellProcess;
171

172
173
174
175
    if (fd < 0)
        _shellProcess = new Pty();
    else
        _shellProcess = new Pty(fd);
176

177
    _shellProcess->setUtf8Mode(_emulation->utf8());
178
179

    //connect teletype to emulation backend
180
181
182
183
    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)));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
184
185
    connect(_emulation, SIGNAL(lockPtyRequest(bool)), _shellProcess, SLOT(lockPty(bool)));
    connect(_emulation, SIGNAL(useUtf8Request(bool)), _shellProcess, SLOT(setUtf8Mode(bool)));
186
187
    connect(_shellProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(done(int,QProcess::ExitStatus)));
    connect(_emulation, SIGNAL(imageSizeChanged(int,int)), this, SLOT(updateWindowSize(int,int)));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
188
    connect(_emulation, SIGNAL(imageSizeInitialized()), this, SLOT(run()));
Robert Knight's avatar
   
Robert Knight committed
189
190
}

191
192
193
194
195
196
197
198
199
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.
    //
200
201
    // If there are no views, the window ID is just 0.  If
    // there are multiple views, then the window ID for the
202
203
204
    // top-level window which contains the first view is
    // returned

Kurt Hindenburg's avatar
Kurt Hindenburg committed
205
    if (_views.count() == 0) {
Jekyll Wu's avatar
Jekyll Wu committed
206
        return 0;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
207
    } else {
208
        QWidget* window = _views.first();
209

Kurt Hindenburg's avatar
Kurt Hindenburg committed
210
        Q_ASSERT(window);
211

Kurt Hindenburg's avatar
Kurt Hindenburg committed
212
        while (window->parentWidget() != 0)
213
214
215
216
217
218
            window = window->parentWidget();

        return window->winId();
    }
}

219
220
221
222
223
224
225
226
void Session::setDarkBackground(bool darkBackground)
{
    _hasDarkBackground = darkBackground;
}
bool Session::hasDarkBackground() const
{
    return _hasDarkBackground;
}
227
bool Session::isRunning() const
228
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
229
    return _shellProcess && (_shellProcess->state() == QProcess::Running);
230
231
}

232
233
234
235
236
void Session::setCodec(QTextCodec* codec)
{
    emulation()->setCodec(codec);
}

237
238
bool Session::setCodec(QByteArray name)
{
239
    QTextCodec* codec = QTextCodec::codecForName(name);
240

241
242
243
    if (codec) {
        setCodec(codec);
        return true;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
244
    } else {
245
246
        return false;
    }
247
248
249
250
251
252
253
}

QByteArray Session::codec()
{
    return _emulation->codec()->name();
}

254
void Session::setProgram(const QString& program)
Robert Knight's avatar
   
Robert Knight committed
255
{
256
257
258
259
    _program = ShellCommand::expand(program);
}
void Session::setInitialWorkingDirectory(const QString& dir)
{
260
    _initialWorkingDir = KShell::tildeExpand(ShellCommand::expand(dir));
Robert Knight's avatar
   
Robert Knight committed
261
}
262
void Session::setArguments(const QStringList& arguments)
Robert Knight's avatar
   
Robert Knight committed
263
{
264
    _arguments = ShellCommand::expand(arguments);
Robert Knight's avatar
   
Robert Knight committed
265
266
}

Robert Knight's avatar
   
Robert Knight committed
267
268
269
QString Session::currentWorkingDirectory()
{
    // only returned cached value
270
271
272
    if (_currentWorkingDir.isEmpty())
        updateWorkingDirectory();

Robert Knight's avatar
   
Robert Knight committed
273
274
275
276
    return _currentWorkingDir;
}
ProcessInfo* Session::updateWorkingDirectory()
{
277
    ProcessInfo* process = getProcessInfo();
Robert Knight's avatar
   
Robert Knight committed
278
279
280
281
    _currentWorkingDir = process->validCurrentDir();
    return process;
}

282
QList<TerminalDisplay*> Session::views() const
283
284
285
286
{
    return _views;
}

287
void Session::addView(TerminalDisplay* widget)
288
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
289
    Q_ASSERT(!_views.contains(widget));
290
291
292

    _views.append(widget);

Kurt Hindenburg's avatar
Kurt Hindenburg committed
293
    if (_emulation != 0) {
Robert Knight's avatar
   
Robert Knight committed
294
        // connect emulation - view signals and slots
295
        connect(widget, SIGNAL(keyPressedSignal(QKeyEvent*)), _emulation,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
296
                SLOT(sendKeyEvent(QKeyEvent*)));
297
298
299
        connect(widget, SIGNAL(mouseSignal(int,int,int,int)), _emulation,
                SLOT(sendMouseEvent(int,int,int,int)));
        connect(widget, SIGNAL(sendStringToEmu(const char*)), _emulation,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
300
                SLOT(sendString(const char*)));
Robert Knight's avatar
   
Robert Knight committed
301

302
        // allow emulation to notify view when the foreground process
303
        // indicates whether or not it is interested in mouse signals
304
        connect(_emulation, SIGNAL(programUsesMouseChanged(bool)), widget,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
305
                SLOT(setUsesMouse(bool)));
306

Kurt Hindenburg's avatar
Kurt Hindenburg committed
307
        widget->setUsesMouse(_emulation->programUsesMouse());
308

309
        widget->setScreenWindow(_emulation->createWindow());
Robert Knight's avatar
   
Robert Knight committed
310
    }
Robert Knight's avatar
   
Robert Knight committed
311

Robert Knight's avatar
   
Robert Knight committed
312
    //connect view signals and slots
313
314
    QObject::connect(widget, SIGNAL(changedContentSizeSignal(int,int)), this,
                     SLOT(onViewSizeChange(int,int)));
315

316
    QObject::connect(widget, SIGNAL(destroyed(QObject*)), this,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
317
                     SLOT(viewDestroyed(QObject*)));
318
319
}

320
void Session::viewDestroyed(QObject* view)
321
{
322
    TerminalDisplay* display = (TerminalDisplay*)view;
323

Kurt Hindenburg's avatar
Kurt Hindenburg committed
324
    Q_ASSERT(_views.contains(display));
325

326
    removeView(display);
327
328
}

329
void Session::removeView(TerminalDisplay* widget)
330
331
332
{
    _views.removeAll(widget);

Kurt Hindenburg's avatar
Kurt Hindenburg committed
333
    disconnect(widget, 0, this, 0);
334

Kurt Hindenburg's avatar
Kurt Hindenburg committed
335
    if (_emulation != 0) {
Robert Knight's avatar
   
Robert Knight committed
336
        // disconnect
337
        //  - key presses signals from widget
Robert Knight's avatar
   
Robert Knight committed
338
339
340
341
        //  - mouse activity signals from widget
        //  - string sending signals from widget
        //
        //  ... and any other signals connected in addView()
Kurt Hindenburg's avatar
Kurt Hindenburg committed
342
        disconnect(widget, 0, _emulation, 0);
343

Robert Knight's avatar
   
Robert Knight committed
344
        // disconnect state change signals emitted by emulation
345
        disconnect(_emulation, 0, widget, 0);
Robert Knight's avatar
   
Robert Knight committed
346
    }
347

348
    // close the session automatically when the last view is removed
Kurt Hindenburg's avatar
Kurt Hindenburg committed
349
    if (_views.count() == 0) {
350
351
        close();
    }
352
353
}

354
QString Session::checkProgram(const QString& program) const
355
{
Jekyll Wu's avatar
Jekyll Wu committed
356
357
358
    // 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);
359

Jekyll Wu's avatar
Jekyll Wu committed
360
361
    if (exec.isEmpty())
        return QString();
362

Jekyll Wu's avatar
Jekyll Wu committed
363
364
    exec = KRun::binaryName(exec, false);
    exec = KShell::tildeExpand(exec);
365
    QString pexec = KStandardDirs::findExe(exec);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
366
    if (pexec.isEmpty()) {
Jekyll Wu's avatar
Jekyll Wu committed
367
368
369
        kError() << i18n("Could not find binary: ") << exec;
        return QString();
    }
370

Jekyll Wu's avatar
Jekyll Wu committed
371
    return exec;
372
373
374
375
}

void Session::terminalWarning(const QString& message)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
376
    static const QByteArray warningText = i18nc("@info:shell Alert the user with red color text", "Warning: ").toLocal8Bit();
377
    QByteArray messageText = message.toLocal8Bit();
378

379
380
    static const char redPenOn[] = "\033[1m\033[31m";
    static const char redPenOff[] = "\033[0m";
381

382
    _emulation->receiveData(redPenOn, qstrlen(redPenOn));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
383
    _emulation->receiveData("\n\r\n\r", 4);
384
385
    _emulation->receiveData(warningText.constData(), qstrlen(warningText.constData()));
    _emulation->receiveData(messageText.constData(), qstrlen(messageText.constData()));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
386
    _emulation->receiveData("\n\r\n\r", 4);
387
    _emulation->receiveData(redPenOff, qstrlen(redPenOff));
388
}
389
390
391
392
393
394
395
396
397

QString Session::shellSessionId() const
{
    QString friendlyUuid(_uniqueIdentifier.toString());
    friendlyUuid.remove('-').remove('{').remove('}');

    return friendlyUuid;
}

398
399
void Session::run()
{
400
    // extra safeguard for potential bug.
Kurt Hindenburg's avatar
Kurt Hindenburg committed
401
    if (isRunning()) {
402
403
404
405
        kWarning() << "Attempted to re-run an already running session.";
        return;
    }

Jekyll Wu's avatar
Jekyll Wu committed
406
    //check that everything is in place to run the session
Kurt Hindenburg's avatar
Kurt Hindenburg committed
407
    if (_program.isEmpty()) {
Jekyll Wu's avatar
Jekyll Wu committed
408
409
        kWarning() << "Session::run() - program to run not set.";
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
410
    if (_arguments.isEmpty()) {
Jekyll Wu's avatar
Jekyll Wu committed
411
412
        kWarning() << "Session::run() - no command line arguments specified.";
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
413
    if (_uniqueIdentifier.isNull()) {
Jekyll Wu's avatar
Jekyll Wu committed
414
415
416
417
418
419
        _uniqueIdentifier = createUuid();
    }

    const int CHOICE_COUNT = 3;
    // if '_program' is empty , fall back to default shell. If that is not set
    // then fall back to /bin/sh
Kurt Hindenburg's avatar
Kurt Hindenburg committed
420
    QString programs[CHOICE_COUNT] = {_program, qgetenv("SHELL"), "/bin/sh"};
Jekyll Wu's avatar
Jekyll Wu committed
421
422
    QString exec;
    int choice = 0;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
423
    while (choice < CHOICE_COUNT) {
Jekyll Wu's avatar
Jekyll Wu committed
424
425
426
427
428
429
430
431
        exec = checkProgram(programs[choice]);
        if (exec.isEmpty())
            choice++;
        else
            break;
    }

    // if a program was specified via setProgram(), but it couldn't be found, print a warning
Kurt Hindenburg's avatar
Kurt Hindenburg committed
432
433
    if (choice != 0 && choice < CHOICE_COUNT && !_program.isEmpty()) {
        terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.", _program, exec));
Jekyll Wu's avatar
Jekyll Wu committed
434
435
    }
    // if none of the choices are available, print a warning
Kurt Hindenburg's avatar
Kurt Hindenburg committed
436
    else if (choice == CHOICE_COUNT) {
Jekyll Wu's avatar
Jekyll Wu committed
437
438
439
440
441
442
        terminalWarning(i18n("Could not find an interactive shell to start."));
        return;
    }

    // if no arguments are specified, fall back to program name
    QStringList arguments = _arguments.join(QChar(' ')).isEmpty() ?
Kurt Hindenburg's avatar
Kurt Hindenburg committed
443
444
                            QStringList() << exec :
                            _arguments;
Jekyll Wu's avatar
Jekyll Wu committed
445
446

    QString dbusService = QDBusConnection::sessionBus().baseService();
447

Jekyll Wu's avatar
Jekyll Wu committed
448
    if (!_initialWorkingDir.isEmpty())
449
        _shellProcess->setInitialWorkingDirectory(_initialWorkingDir);
450
    else
451
        _shellProcess->setInitialWorkingDirectory(QDir::homePath());
Jekyll Wu's avatar
Jekyll Wu committed
452
453

    _shellProcess->setFlowControlEnabled(_flowControl);
454
    _shellProcess->setEraseChar(_emulation->eraseChar());
Jekyll Wu's avatar
Jekyll Wu committed
455
456
457
458
459
460
461
462
463

    // 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";
    _environment << backgroundColorHint;
    _environment << QString("SHELL_SESSION_ID=%1").arg(shellSessionId());

Kurt Hindenburg's avatar
Kurt Hindenburg committed
464
465
466
467
468
469
470
471
472
473
    int result = _shellProcess->start(exec,
                                      arguments,
                                      _environment,
                                      windowId(),
                                      _addToUtmp,
                                      dbusService,
                                      (QLatin1String("/Sessions/") +
                                       QString::number(_sessionId)));

    if (result < 0) {
Jekyll Wu's avatar
Jekyll Wu committed
474
475
476
477
478
479
480
        terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec, arguments.join(" ")));
        return;
    }

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

    emit started();
481
482
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
483
void Session::setUserTitle(int what, const QString& caption)
484
{
485
    //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle )
486
    bool modified = false;
487

Kurt Hindenburg's avatar
Kurt Hindenburg committed
488
489
    if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) {
        if (_userTitle != caption) {
490
491
492
            _userTitle = caption;
            modified = true;
        }
493
    }
494

Kurt Hindenburg's avatar
Kurt Hindenburg committed
495
496
    if ((what == IconNameAndWindowTitle) || (what == IconName)) {
        if (_iconText != caption) {
Jekyll Wu's avatar
Jekyll Wu committed
497
            _iconText = caption;
498
499
500
            modified = true;
        }
    }
501

Kurt Hindenburg's avatar
Kurt Hindenburg committed
502
503
    if (what == TextColor || what == BackgroundColor) {
        QString colorString = caption.section(';', 0, 0);
Jekyll Wu's avatar
Jekyll Wu committed
504
        QColor color = QColor(colorString);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
505
        if (color.isValid()) {
Jekyll Wu's avatar
Jekyll Wu committed
506
507
508
509
510
            if (what == TextColor)
                emit changeForegroundColorRequest(color);
            else
                emit changeBackgroundColorRequest(color);
        }
511
    }
512

Kurt Hindenburg's avatar
Kurt Hindenburg committed
513
514
515
    if (what == SessionName) {
        if (_nameTitle != caption) {
            setTitle(Session::NameRole, caption);
516
517
518
            return;
        }
    }
519

Kurt Hindenburg's avatar
Kurt Hindenburg committed
520
521
522
    if (what == 31) {
        QString cwd = caption;
        cwd = cwd.replace(QRegExp("^~"), QDir::homePath());
Jekyll Wu's avatar
Jekyll Wu committed
523
        emit openUrlRequest(cwd);
524
    }
525

Robert Knight's avatar
   
Robert Knight committed
526
    // change icon via \033]32;Icon\007
Kurt Hindenburg's avatar
Kurt Hindenburg committed
527
528
    if (what == 32) {
        if (_iconName != caption) {
Jekyll Wu's avatar
Jekyll Wu committed
529
            _iconName = caption;
530

531
532
            modified = true;
        }
533
    }
534

Kurt Hindenburg's avatar
Kurt Hindenburg committed
535
    if (what == ProfileChange) {
536
537
538
539
        emit profileChangeCommandReceived(caption);
        return;
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
540
    if (modified)
541
        emit titleChanged();
542
543
}

544
QString Session::userTitle() const
Robert Knight's avatar
Robert Knight committed
545
546
547
{
    return _userTitle;
}
548
549
void Session::setTabTitleFormat(TabTitleContext context , const QString& format)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
550
    if (context == LocalTabTitle)
551
        _localTabTitleFormat = format;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
552
    else if (context == RemoteTabTitle)
553
554
555
        _remoteTabTitleFormat = format;
}
QString Session::tabTitleFormat(TabTitleContext context) const
Robert Knight's avatar
Robert Knight committed
556
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
557
    if (context == LocalTabTitle)
558
        return _localTabTitleFormat;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
559
    else if (context == RemoteTabTitle)
560
561
        return _remoteTabTitleFormat;

562
    return QString();
Robert Knight's avatar
Robert Knight committed
563
564
}

565
void Session::silenceTimerDone()
566
{
Jekyll Wu's avatar
Jekyll Wu committed
567
568
569
570
571
572
573
    //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


Kurt Hindenburg's avatar
Kurt Hindenburg committed
574
    //FIXME: Make message text for this notification and the activity notification more descriptive.
Jekyll Wu's avatar
Jekyll Wu committed
575
576
    if (_monitorSilence) {
        KNotification::event("Silence", i18n("Silence in session '%1'", _nameTitle), QPixmap(),
Kurt Hindenburg's avatar
Kurt Hindenburg committed
577
578
                             QApplication::activeWindow(),
                             KNotification::CloseWhenWidgetActivated);
Jekyll Wu's avatar
Jekyll Wu committed
579
        emit stateChanged(NOTIFYSILENCE);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
580
    } else {
Jekyll Wu's avatar
Jekyll Wu committed
581
582
583
        emit stateChanged(NOTIFYNORMAL);
    }

584
    _notifiedActivity = false;
585
}
586
587
588
589
590
591

void Session::activityTimerDone()
{
    _notifiedActivity = false;
}

Robert Knight's avatar
   
Robert Knight committed
592
593
void Session::updateFlowControlState(bool suspended)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
594
595
596
    if (suspended) {
        if (flowControlEnabled()) {
            foreach(TerminalDisplay * display, _views) {
Robert Knight's avatar
   
Robert Knight committed
597
598
599
600
                if (display->flowControlWarningEnabled())
                    display->outputSuspended(true);
            }
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
601
    } else {
Jekyll Wu's avatar
Jekyll Wu committed
602
603
604
        foreach(TerminalDisplay * display, _views) {
            display->outputSuspended(false);
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
605
    }
Robert Knight's avatar
   
Robert Knight committed
606
}
607
608
609
610
611
612

void Session::onPrimaryScreenInUse(bool use)
{
    emit primaryScreenInUse(use);
}

613
614
615
616
617
void Session::onSelectedText(const QString & text)
{
    emit selectedText(text);
}

618
void Session::activityStateSet(int state)
619
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
620
621
622
623
    if (state == NOTIFYBELL) {
        emit bellRequest(i18n("Bell in session '%1'", _nameTitle));
    } else if (state == NOTIFYACTIVITY) {
        if (_monitorActivity  && !_notifiedActivity) {
624
            KNotification::event("Activity", i18n("Activity in session '%1'", _nameTitle), QPixmap(),
Kurt Hindenburg's avatar
Kurt Hindenburg committed
625
626
                                 QApplication::activeWindow(),
                                 KNotification::CloseWhenWidgetActivated);
627
628
629
630
631

            // mask activity notification for a while to avoid flooding
            // TODO: should this hardcoded interval be user configurable?
            _notifiedActivity = true;
            const int activitMaskSeconds = 15;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
632
            _activityTimer->start(activitMaskSeconds * 1000);
Jekyll Wu's avatar
Jekyll Wu committed
633
        }
634

Kurt Hindenburg's avatar
Kurt Hindenburg committed
635
636
        if (_monitorSilence) {
            _silenceTimer->start(_silenceSeconds * 1000);
Jekyll Wu's avatar
Jekyll Wu committed
637
        }
638
    }
639

Kurt Hindenburg's avatar
Kurt Hindenburg committed
640
    if (state == NOTIFYACTIVITY && !_monitorActivity)
Jekyll Wu's avatar
Jekyll Wu committed
641
        state = NOTIFYNORMAL;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
642
    if (state == NOTIFYSILENCE && !_monitorSilence)
Jekyll Wu's avatar
Jekyll Wu committed
643
        state = NOTIFYNORMAL;
644

Jekyll Wu's avatar
Jekyll Wu committed
645
    emit stateChanged(state);
646
}
647

648
void Session::onViewSizeChange(int /*height*/, int /*width*/)
649
{
Jekyll Wu's avatar
Jekyll Wu committed
650
    updateTerminalSize();
651
652
}

653
void Session::updateTerminalSize()
654
{
655
    QListIterator<TerminalDisplay*> viewIter(_views);
656
657
658
659

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

660
    // minimum number of lines and columns that views require for
661
662
663
664
665
    // 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;

666
    //select largest number of lines and columns that will fit in all visible views
Kurt Hindenburg's avatar
Kurt Hindenburg committed
667
    while (viewIter.hasNext()) {
668
        TerminalDisplay* view = viewIter.next();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
669
670
671
672
673
        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());
674
            view->processFilters();
675
        }
676
    }
677

678
    // backend emulation must have a _terminal of at least 1 column x 1 line in size
Kurt Hindenburg's avatar
Kurt Hindenburg committed
679
680
    if (minLines > 0 && minColumns > 0) {
        _emulation->setImageSize(minLines , minColumns);
681
682
    }
}
683
684
685
void Session::updateWindowSize(int lines, int columns)
{
    Q_ASSERT(lines > 0 && columns > 0);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
686
    _shellProcess->setWindowSize(lines, columns);
687
}
688
689
690
691
692
693
694
695
696
697
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
698
    // in the window size (some shells do nothing if the
699
700
    // new and old sizes are the same)
    //
701
    // if there is a more 'correct' way to do this, please
702
703
704
    // send an email with method or patches to konsole-devel@kde.org

    const QSize existingSize = _shellProcess->windowSize();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
705
706
    _shellProcess->setWindowSize(existingSize.height(), existingSize.width() + 1);
    _shellProcess->setWindowSize(existingSize.height(), existingSize.width());
707
708
}

709
bool Session::kill(int signal)
710
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
711
    int result = ::kill(_shellProcess->pid(), signal);
712

Kurt Hindenburg's avatar
Kurt Hindenburg committed
713
714
    if (result == 0) {
        if (_shellProcess->waitForFinished(1000))
715
716
717
            return true;
        else
            return false;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
718
    } else
719
        return false;
720
721
}

722
void Session::close()
723
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
724
725
    if (isRunning()) {
        if (!closeInNormalWay())
726
            closeInForceWay();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
727
    } else {
728
729
730
731
732
733
734
735
        // terminal process has finished, just close the session
        QTimer::singleShot(1, this, SIGNAL(finished()));
    }
}

bool Session::closeInNormalWay()
{
    _autoClose    = true;
736
    _closePerUserRequest = true;
737

738
739
740
741
742
743
    // for the possible case where following events happen in sequence:
    //
    // 1). the terminal process crashes
    // 2). the tab stays open and displays warning message
    // 3). the user closes the tab explicitly
    //
Kurt Hindenburg's avatar
Kurt Hindenburg committed
744
    if (!isRunning()) {
745
746
747
748
        emit finished();
        return true;
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
749
    if (kill(SIGHUP)) {
750
        return true;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
751
    } else {
752
753
754
755
756
        kWarning() << "Process " << _shellProcess->pid() << " did not die with SIGHUP";
        _shellProcess->pty()->close();
        return (_shellProcess->waitForFinished(1000)) ;
    }
}
757

758
759
760
bool Session::closeInForceWay()
{
    _autoClose    = true;
761
    _closePerUserRequest = true;
762

Kurt Hindenburg's avatar
Kurt Hindenburg committed
763
    if (kill(SIGKILL)) {
764
        return true;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
765
    } else {
766
767
        kWarning() << "Process " << _shellProcess->pid() << " did not die with SIGKILL";
        return false;
Jekyll Wu's avatar
Jekyll Wu committed
768
    }
769
770
}

771
void Session::sendText(const QString& text) const
772
{
Jekyll Wu's avatar
Jekyll Wu committed
773
    _emulation->sendText(text);
774
775
}

776
777
778
779
780
void Session::sendMouseEvent(int buttons, int column, int line, int eventType)
{
    _emulation->sendMouseEvent(buttons, column, line, eventType);
}

781
Session::~Session()
782
{
Jekyll Wu's avatar
Jekyll Wu committed
783
784
785
786
787
    delete _foregroundProcessInfo;
    delete _sessionProcessInfo;
    delete _emulation;
    delete _shellProcess;
    delete _zmodemProc;
788
789
}

790
void Session::done(int exitCode, QProcess::ExitStatus exitStatus)
791
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
792
    if (!_autoClose) {
Jekyll Wu's avatar
Jekyll Wu committed
793
794
795
796
        _userTitle = i18nc("@info:shell This session is done", "Finished");
        emit titleChanged();
        return;
    }
Robert Knight's avatar
Robert Knight committed
797

Kurt Hindenburg's avatar
Kurt Hindenburg committed
798
    if (_closePerUserRequest) {
799
800
801
802
        emit finished();
        return;
    }

Jekyll Wu's avatar
Jekyll Wu committed
803
    QString message;
804

Kurt Hindenburg's avatar
Kurt Hindenburg committed
805
    if (exitCode != 0) {
806
        if (exitStatus != QProcess::NormalExit)
Jekyll Wu's avatar
Jekyll Wu committed
807
            message = i18n("Program '%1' crashed.", _program);
808
809
        else
            message = i18n("Program '%1' exited with status %2.", _program, exitCode);
Jekyll Wu's avatar
Jekyll Wu committed
810

811
        //FIXME: See comments in Session::silenceTimerDone()
Jekyll Wu's avatar
Jekyll Wu committed
812
        KNotification::event("Finished", message , QPixmap(),
Kurt Hindenburg's avatar
Kurt Hindenburg committed
813
814
                             QApplication::activeWindow(),
                             KNotification::CloseWhenWidgetActivated);
Jekyll Wu's avatar
Jekyll Wu committed
815
    }
816

Kurt Hindenburg's avatar
Kurt Hindenburg committed
817
    if (exitStatus != QProcess::NormalExit) {
818
819
        // this seeming duplicated line is for the case when exitCode is 0
        message = i18n("Program '%1' crashed.", _program);
Jekyll Wu's avatar
Jekyll Wu committed
820
        terminalWarning(message);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
821
    } else {
822
        emit finished();
823
    }
824
825
}

826
Emulation* Session::emulation() const
827
{
Jekyll Wu's avatar
Jekyll Wu committed
828
    return _emulation;
829
830
}

831
QString Session::keyBindings() const
832
{
Jekyll Wu's avatar
Jekyll Wu committed
833
    return _emulation->keyBindings();
834
835
}

836
QStringList Session::environment() const
837
{
Jekyll Wu's avatar
Jekyll Wu committed
838
    return _environment;
839
840
}

841
void Session::setEnvironment(const QStringList& environment)
842
{
843
    _environment = environment;
844
845
}

846
847
848
849
850
void Session::addEnvironmentEntry(const QString& entry)
{
    _environment << entry;
}

851
int Session::sessionId() const
852
{
Jekyll Wu's avatar
Jekyll Wu committed
853
    return _sessionId;
Robert Knight's avatar
   
Robert Knight committed
854
}
855

856
void Session::setKeyBindings(const QString& id)
857
{
Jekyll Wu's avatar
Jekyll Wu committed
858
    _emulation->setKeyBindings(id);
859
860
}

861
void Session::setTitle(TitleRole role , const QString& newTitle)