Session.cpp 44.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
    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
#include <unistd.h>
31

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

41
// KDE
Laurent Montel's avatar
Laurent Montel committed
42
#include <QDebug>
43
#include <KLocalizedString>
44
45
#include <KNotification>
#include <KRun>
46
#include <KShell>
47
48
#include <KProcess>
#include <KConfigGroup>
49

50
// Konsole
51
52
#include <sessionadaptor.h>

Robert Knight's avatar
   
Robert Knight committed
53
#include "ProcessInfo.h"
54
55
#include "Pty.h"
#include "TerminalDisplay.h"
56
#include "ShellCommand.h"
57
#include "Vt102Emulation.h"
58
#include "ZModemDialog.h"
59
#include "History.h"
60

61
using namespace Konsole;
62

63
int Session::lastSessionId = 0;
64
static bool show_disallow_certain_dbus_methods_message = true;
65

66
67
68
69
70
// 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
71
    static const int intbits = sizeof(int) * 8;
72
    static int randbits = 0;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
73
    if (!randbits) {
74
        int max = RAND_MAX;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
75
76
77
        do {
            ++randbits;
        } while ((max = max >> 1));
78
79
    }

80
    qsrand(uint(QDateTime::currentDateTimeUtc().toTime_t()));
81
82
83
    qrand(); // Skip first

    QUuid result;
84
    uint* data = &(result.data1);
85
86
87
88
    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
89
90
            randNumber |= qrand() << filled;
        *(data + chunks) = randNumber;
91
92
93
94
95
96
97
98
    }

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

    return result;
}

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

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

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

Laurent Montel's avatar
Laurent Montel committed
130
131
132
133
134
135
136
137
138
    connect(_emulation, &Konsole::Emulation::titleChanged, this, &Konsole::Session::setUserTitle);
    connect(_emulation, &Konsole::Emulation::stateSet, this, &Konsole::Session::activityStateSet);
    connect(_emulation, &Konsole::Emulation::zmodemDetected, this, &Konsole::Session::fireZModemDetected);
    connect(_emulation, &Konsole::Emulation::changeTabTextColorRequest, this, &Konsole::Session::changeTabTextColorRequest);
    connect(_emulation, &Konsole::Emulation::profileChangeCommandReceived, this, &Konsole::Session::profileChangeCommandReceived);
    connect(_emulation, &Konsole::Emulation::flowControlKeyPressed, this, &Konsole::Session::updateFlowControlState);
    connect(_emulation, &Konsole::Emulation::primaryScreenInUse, this, &Konsole::Session::onPrimaryScreenInUse);
    connect(_emulation, &Konsole::Emulation::selectionChanged, this, &Konsole::Session::selectionChanged);
    connect(_emulation, &Konsole::Emulation::imageResizeRequest, this, &Konsole::Session::resizeRequest);
139
    connect(_emulation, &Konsole::Emulation::sessionAttributeRequest, this, &Konsole::Session::sessionAttributeRequest);
Robert Knight's avatar
   
Robert Knight committed
140

141
142
    //create new teletype for I/O with shell process
    openTeletype(-1);
143

Jekyll Wu's avatar
Jekyll Wu committed
144
    //setup timer for monitoring session activity & silence
145
146
    _silenceTimer = new QTimer(this);
    _silenceTimer->setSingleShot(true);
147
    connect(_silenceTimer, &QTimer::timeout, this, &Konsole::Session::silenceTimerDone);
148

Kurt Hindenburg's avatar
Kurt Hindenburg committed
149
    _activityTimer = new QTimer(this);
150
    _activityTimer->setSingleShot(true);
151
    connect(_activityTimer, &QTimer::timeout, this, &Konsole::Session::activityTimerDone);
152
153
}

Jekyll Wu's avatar
Jekyll Wu committed
154
155
156
157
158
159
160
161
162
Session::~Session()
{
    delete _foregroundProcessInfo;
    delete _sessionProcessInfo;
    delete _emulation;
    delete _shellProcess;
    delete _zmodemProc;
}

163
164
void Session::openTeletype(int fd)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
165
    if (isRunning()) {
Laurent Montel's avatar
Laurent Montel committed
166
        qWarning() << "Attempted to open teletype in a running session.";
167
168
        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 the I/O between emulator and pty process
Laurent Montel's avatar
Laurent Montel committed
180
181
    connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock);
    connect(_emulation, &Konsole::Emulation::sendData, _shellProcess, &Konsole::Pty::sendData);
182
183

    // UTF8 mode
Laurent Montel's avatar
Laurent Montel committed
184
    connect(_emulation, &Konsole::Emulation::useUtf8Request, _shellProcess, &Konsole::Pty::setUtf8Mode);
185
186

    // get notified when the pty process is finished
Laurent Montel's avatar
Laurent Montel committed
187
    connect(_shellProcess, static_cast<void(Pty::*)(int,QProcess::ExitStatus)>(&Konsole::Pty::finished), this, &Konsole::Session::done);
188
189

    // emulator size
190
191
    // Use a direct connection to ensure that the window size is set before it runs
    connect(_emulation, &Konsole::Emulation::imageSizeChanged, this, &Konsole::Session::updateWindowSize, Qt::DirectConnection);
Laurent Montel's avatar
Laurent Montel committed
192
    connect(_emulation, &Konsole::Emulation::imageSizeInitialized, this, &Konsole::Session::run);
Robert Knight's avatar
   
Robert Knight committed
193
194
}

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
209
    if (_views.count() == 0) {
Jekyll Wu's avatar
Jekyll Wu committed
210
        return 0;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
211
    } else {
212
        QWidget* window = _views.first();
213

Kurt Hindenburg's avatar
Kurt Hindenburg committed
214
        Q_ASSERT(window);
215

Kurt Hindenburg's avatar
Kurt Hindenburg committed
216
        while (window->parentWidget() != 0)
217
218
219
220
221
222
            window = window->parentWidget();

        return window->winId();
    }
}

223
224
225
226
void Session::setDarkBackground(bool darkBackground)
{
    _hasDarkBackground = darkBackground;
}
Jekyll Wu's avatar
Jekyll Wu committed
227

228
bool Session::isRunning() const
229
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
230
    return _shellProcess && (_shellProcess->state() == QProcess::Running);
231
232
}

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
238
bool Session::setCodec(const QByteArray& name)
239
{
240
    QTextCodec* codec = QTextCodec::codecForName(name);
241

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

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

255
void Session::setProgram(const QString& program)
Robert Knight's avatar
   
Robert Knight committed
256
{
257
258
    _program = ShellCommand::expand(program);
}
Jekyll Wu's avatar
Jekyll Wu committed
259

260
void Session::setArguments(const QStringList& arguments)
Robert Knight's avatar
   
Robert Knight committed
261
{
262
    _arguments = ShellCommand::expand(arguments);
Robert Knight's avatar
   
Robert Knight committed
263
264
}

Jekyll Wu's avatar
Jekyll Wu committed
265
266
267
268
269
void Session::setInitialWorkingDirectory(const QString& dir)
{
    _initialWorkingDir = KShell::tildeExpand(ShellCommand::expand(dir));
}

Robert Knight's avatar
   
Robert Knight committed
270
271
QString Session::currentWorkingDirectory()
{
272
273
274
275
    if (_reportedWorkingUrl.isValid() && _reportedWorkingUrl.isLocalFile()) {
        return _reportedWorkingUrl.path();
    }

Robert Knight's avatar
   
Robert Knight committed
276
    // only returned cached value
277
278
279
    if (_currentWorkingDir.isEmpty())
        updateWorkingDirectory();

Robert Knight's avatar
   
Robert Knight committed
280
281
    return _currentWorkingDir;
}
282
void Session::updateWorkingDirectory()
Robert Knight's avatar
   
Robert Knight committed
283
{
284
    updateSessionProcessInfo();
285

286
    const QString currentDir = _sessionProcessInfo->validCurrentDir();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
287
    if (currentDir != _currentWorkingDir) {
288
289
290
        _currentWorkingDir = currentDir;
        emit currentDirectoryChanged(_currentWorkingDir);
    }
Robert Knight's avatar
   
Robert Knight committed
291
292
}

293
QList<TerminalDisplay*> Session::views() const
294
295
296
297
{
    return _views;
}

298
void Session::addView(TerminalDisplay* widget)
299
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
300
    Q_ASSERT(!_views.contains(widget));
301
302
303

    _views.append(widget);

304
    // connect emulation - view signals and slots
Laurent Montel's avatar
Laurent Montel committed
305
306
307
    connect(widget, &Konsole::TerminalDisplay::keyPressedSignal, _emulation, &Konsole::Emulation::sendKeyEvent);
    connect(widget, &Konsole::TerminalDisplay::mouseSignal, _emulation, &Konsole::Emulation::sendMouseEvent);
    connect(widget, &Konsole::TerminalDisplay::sendStringToEmu, _emulation, &Konsole::Emulation::sendString);
308
309
310

    // allow emulation to notify view when the foreground process
    // indicates whether or not it is interested in mouse signals
Laurent Montel's avatar
Laurent Montel committed
311
    connect(_emulation, &Konsole::Emulation::programUsesMouseChanged, widget, &Konsole::TerminalDisplay::setUsesMouse);
312
313
314

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

Laurent Montel's avatar
Laurent Montel committed
315
    connect(_emulation, &Konsole::Emulation::programBracketedPasteModeChanged, widget, &Konsole::TerminalDisplay::setBracketedPasteMode);
316
317
318

    widget->setBracketedPasteMode(_emulation->programBracketedPasteMode());

319
    widget->setScreenWindow(_emulation->createWindow());
Robert Knight's avatar
   
Robert Knight committed
320

Robert Knight's avatar
   
Robert Knight committed
321
    //connect view signals and slots
Laurent Montel's avatar
Laurent Montel committed
322
    connect(widget, &Konsole::TerminalDisplay::changedContentSizeSignal, this, &Konsole::Session::onViewSizeChange);
323

Laurent Montel's avatar
Laurent Montel committed
324
    connect(widget, &Konsole::TerminalDisplay::destroyed, this, &Konsole::Session::viewDestroyed);
325
326
327

    connect(widget, &Konsole::TerminalDisplay::focusLost, _emulation, &Konsole::Emulation::focusLost);
    connect(widget, &Konsole::TerminalDisplay::focusGained, _emulation, &Konsole::Emulation::focusGained);
328
329
}

330
void Session::viewDestroyed(QObject* view)
331
{
Jekyll Wu's avatar
Jekyll Wu committed
332
333
334
    // the received QObject has already been destroyed, so using
    // qobject_cast<> does not work here
    TerminalDisplay* display = static_cast<TerminalDisplay*>(view);
335

Kurt Hindenburg's avatar
Kurt Hindenburg committed
336
    Q_ASSERT(_views.contains(display));
337

338
    removeView(display);
339
340
}

341
void Session::removeView(TerminalDisplay* widget)
342
343
344
{
    _views.removeAll(widget);

Kurt Hindenburg's avatar
Kurt Hindenburg committed
345
    disconnect(widget, 0, this, 0);
346

347
348
349
350
351
352
353
354
355
356
    // disconnect
    //  - key presses signals from widget
    //  - mouse activity signals from widget
    //  - string sending signals from widget
    //
    //  ... and any other signals connected in addView()
    disconnect(widget, 0, _emulation, 0);

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

358
    // close the session automatically when the last view is removed
Kurt Hindenburg's avatar
Kurt Hindenburg committed
359
    if (_views.count() == 0) {
360
361
        close();
    }
362
363
}

364
365
// Upon a KPty error, there is no description on what that error was...
// Check to see if the given program is executable.
366
QString Session::checkProgram(const QString& program)
367
{
368
    QString exec = program;
369

Jekyll Wu's avatar
Jekyll Wu committed
370
371
    if (exec.isEmpty())
        return QString();
372

373
374
375
376
377
    QFileInfo info(exec);
    if (info.isAbsolute() && info.exists() && info.isExecutable()) {
        return exec;
    }

Jekyll Wu's avatar
Jekyll Wu committed
378
379
    exec = KRun::binaryName(exec, false);
    exec = KShell::tildeExpand(exec);
380
    const QString pexec = QStandardPaths::findExecutable(exec);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
381
    if (pexec.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
382
        qCritical() << i18n("Could not find binary: ") << exec;
Jekyll Wu's avatar
Jekyll Wu committed
383
384
        return QString();
    }
385

Jekyll Wu's avatar
Jekyll Wu committed
386
    return exec;
387
388
389
390
}

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

394
395
    static const char redPenOn[] = "\033[1m\033[31m";
    static const char redPenOff[] = "\033[0m";
396

397
    _emulation->receiveData(redPenOn, qstrlen(redPenOn));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
398
    _emulation->receiveData("\n\r\n\r", 4);
399
400
    _emulation->receiveData(warningText.constData(), qstrlen(warningText.constData()));
    _emulation->receiveData(messageText.constData(), qstrlen(messageText.constData()));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
401
    _emulation->receiveData("\n\r\n\r", 4);
402
    _emulation->receiveData(redPenOff, qstrlen(redPenOff));
403
}
404
405
406
407
408
409
410
411
412

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

    return friendlyUuid;
}

413
414
void Session::run()
{
415
    // extra safeguard for potential bug.
Kurt Hindenburg's avatar
Kurt Hindenburg committed
416
    if (isRunning()) {
Laurent Montel's avatar
Laurent Montel committed
417
        qWarning() << "Attempted to re-run an already running session.";
418
419
420
        return;
    }

Jekyll Wu's avatar
Jekyll Wu committed
421
    //check that everything is in place to run the session
Kurt Hindenburg's avatar
Kurt Hindenburg committed
422
    if (_program.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
423
        qWarning() << "Program to run not set.";
Jekyll Wu's avatar
Jekyll Wu committed
424
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
425
    if (_arguments.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
426
        qWarning() << "No command line arguments specified.";
Jekyll Wu's avatar
Jekyll Wu committed
427
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
428
    if (_uniqueIdentifier.isNull()) {
Jekyll Wu's avatar
Jekyll Wu committed
429
430
431
432
433
434
        _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
435
    QString programs[CHOICE_COUNT] = {_program, qgetenv("SHELL"), "/bin/sh"};
Jekyll Wu's avatar
Jekyll Wu committed
436
437
    QString exec;
    int choice = 0;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
438
    while (choice < CHOICE_COUNT) {
Jekyll Wu's avatar
Jekyll Wu committed
439
440
441
442
443
444
445
446
        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
447
448
    if (choice != 0 && choice < CHOICE_COUNT && !_program.isEmpty()) {
        terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.", _program, exec));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
449
        // if none of the choices are available, print a warning
450
    } else if (choice == CHOICE_COUNT) {
Jekyll Wu's avatar
Jekyll Wu committed
451
452
453
454
455
456
        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
457
458
                            QStringList() << exec :
                            _arguments;
Jekyll Wu's avatar
Jekyll Wu committed
459

460
    if (!_initialWorkingDir.isEmpty()) {
461
        _shellProcess->setInitialWorkingDirectory(_initialWorkingDir);
462
463
464
    } else {
        _shellProcess->setInitialWorkingDirectory(QDir::currentPath());
    }
Jekyll Wu's avatar
Jekyll Wu committed
465

Jekyll Wu's avatar
Jekyll Wu committed
466
    _shellProcess->setFlowControlEnabled(_flowControlEnabled);
467
    _shellProcess->setEraseChar(_emulation->eraseChar());
468
    _shellProcess->setUseUtmp(_addToUtmp);
Jekyll Wu's avatar
Jekyll Wu committed
469
470
471
472
473

    // 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
474
    const QString backgroundColorHint = _hasDarkBackground ? "COLORFGBG=15;0" : "COLORFGBG=0;15";
Kurt Hindenburg's avatar
Kurt Hindenburg committed
475
    addEnvironmentEntry(backgroundColorHint);
476

Kurt Hindenburg's avatar
Kurt Hindenburg committed
477
    addEnvironmentEntry(QString("SHELL_SESSION_ID=%1").arg(shellSessionId()));
Jekyll Wu's avatar
Jekyll Wu committed
478

Kurt Hindenburg's avatar
Kurt Hindenburg committed
479
    addEnvironmentEntry(QString("WINDOWID=%1").arg(QString::number(windowId())));
480
481

    const QString dbusService = QDBusConnection::sessionBus().baseService();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
482
    addEnvironmentEntry(QString("KONSOLE_DBUS_SERVICE=%1").arg(dbusService));
483
484

    const QString dbusObject = QString("/Sessions/%1").arg(QString::number(_sessionId));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
485
    addEnvironmentEntry(QString("KONSOLE_DBUS_SESSION=%1").arg(dbusObject));
486

487
    int result = _shellProcess->start(exec, arguments, _environment);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
488
    if (result < 0) {
Jekyll Wu's avatar
Jekyll Wu committed
489
        terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec, arguments.join(" ")));
490
        terminalWarning(_shellProcess->errorString());
Jekyll Wu's avatar
Jekyll Wu committed
491
492
493
494
495
496
        return;
    }

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

    emit started();
497
498
}

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
504
505
    if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) {
        if (_userTitle != caption) {
506
507
508
            _userTitle = caption;
            modified = true;
        }
509
    }
510

Kurt Hindenburg's avatar
Kurt Hindenburg committed
511
512
    if ((what == IconNameAndWindowTitle) || (what == IconName)) {
        if (_iconText != caption) {
Jekyll Wu's avatar
Jekyll Wu committed
513
            _iconText = caption;
514
515
516
            modified = true;
        }
    }
517

Kurt Hindenburg's avatar
Kurt Hindenburg committed
518
519
    if (what == TextColor || what == BackgroundColor) {
        QString colorString = caption.section(';', 0, 0);
Jekyll Wu's avatar
Jekyll Wu committed
520
        QColor color = QColor(colorString);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
521
        if (color.isValid()) {
Jekyll Wu's avatar
Jekyll Wu committed
522
523
524
525
526
            if (what == TextColor)
                emit changeForegroundColorRequest(color);
            else
                emit changeBackgroundColorRequest(color);
        }
527
    }
528

Kurt Hindenburg's avatar
Kurt Hindenburg committed
529
    if (what == SessionName) {
530
531
532
533
        if (_localTabTitleFormat != caption) {
            _localTabTitleFormat = caption;
            setTitle(Session::DisplayedTitleRole, caption);
            modified = true;
534
535
        }
    }
536

537
    /* I don't believe this has ever worked in KDE 4.x
Kurt Hindenburg's avatar
Kurt Hindenburg committed
538
539
540
    if (what == 31) {
        QString cwd = caption;
        cwd = cwd.replace(QRegExp("^~"), QDir::homePath());
Jekyll Wu's avatar
Jekyll Wu committed
541
        emit openUrlRequest(cwd);
542
    }*/
543

544
545
546
    /* The below use of 32 works but appears to non-standard.
       It is from a commit from 2004 c20973eca8776f9b4f15bee5fdcb5a3205aa69de
     */
Robert Knight's avatar
   
Robert Knight committed
547
    // change icon via \033]32;Icon\007
548
    if (what == SessionIcon) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
549
        if (_iconName != caption) {
Jekyll Wu's avatar
Jekyll Wu committed
550
            _iconName = caption;
551

552
553
            modified = true;
        }
554
    }
555

556
557
558
559
560
561
    if (what == CurrentDirectory) {
        _reportedWorkingUrl = QUrl::fromUserInput(caption);
        emit currentDirectoryChanged(currentWorkingDirectory());
        modified = true;
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
562
    if (what == ProfileChange) {
563
564
565
566
        emit profileChangeCommandReceived(caption);
        return;
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
567
    if (modified)
568
        emit titleChanged();
569
570
}

571
QString Session::userTitle() const
Robert Knight's avatar
Robert Knight committed
572
573
574
{
    return _userTitle;
}
575
576
void Session::setTabTitleFormat(TabTitleContext context , const QString& format)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
577
    if (context == LocalTabTitle)
578
        _localTabTitleFormat = format;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
579
    else if (context == RemoteTabTitle)
580
581
582
        _remoteTabTitleFormat = format;
}
QString Session::tabTitleFormat(TabTitleContext context) const
Robert Knight's avatar
Robert Knight committed
583
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
584
    if (context == LocalTabTitle)
585
        return _localTabTitleFormat;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
586
    else if (context == RemoteTabTitle)
587
588
        return _remoteTabTitleFormat;

589
    return QString();
Robert Knight's avatar
Robert Knight committed
590
591
}

592
void Session::silenceTimerDone()
593
{
Jekyll Wu's avatar
Jekyll Wu committed
594
595
596
597
598
599
    //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
600
    //FIXME: Make message text for this notification and the activity notification more descriptive.
601
    if (!_monitorSilence) {
Jekyll Wu's avatar
Jekyll Wu committed
602
        emit stateChanged(NOTIFYNORMAL);
603
604
605
606
607
608
609
610
611
        return;
    }

    bool hasFocus = false;
    for (TerminalDisplay *display : _views) {
        if (display->hasFocus()) {
            hasFocus = true;
            break;
        }
Jekyll Wu's avatar
Jekyll Wu committed
612
    }
613
614
615
616
617
618

    KNotification::event(hasFocus ? "Silence" : "SilenceHidden",
            i18n("Silence in session '%1'", _nameTitle), QPixmap(),
            QApplication::activeWindow(),
            KNotification::CloseWhenWidgetActivated);
    emit stateChanged(NOTIFYSILENCE);
619
}
620
621
622
623
624
625

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

Robert Knight's avatar
   
Robert Knight committed
626
627
void Session::updateFlowControlState(bool suspended)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
628
629
630
    if (suspended) {
        if (flowControlEnabled()) {
            foreach(TerminalDisplay * display, _views) {
Robert Knight's avatar
   
Robert Knight committed
631
632
633
634
                if (display->flowControlWarningEnabled())
                    display->outputSuspended(true);
            }
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
635
    } else {
Jekyll Wu's avatar
Jekyll Wu committed
636
637
638
        foreach(TerminalDisplay * display, _views) {
            display->outputSuspended(false);
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
639
    }
Robert Knight's avatar
   
Robert Knight committed
640
}
641
642
643
644
645
646

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

647
648
649
650
651
652
653
654
655
656
void Session::sessionAttributeRequest(int id)
{
    switch (id) {
        case BackgroundColor:
            // Get 'TerminalDisplay' (_view) background color
            emit getBackgroundColor();
            break;
    }
}

657
void Session::activityStateSet(int state)
658
{
659
660
661
    // TODO: should this hardcoded interval be user configurable?
    const int activityMaskInSeconds = 15;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
662
663
664
    if (state == NOTIFYBELL) {
        emit bellRequest(i18n("Bell in session '%1'", _nameTitle));
    } else if (state == NOTIFYACTIVITY) {
665
666
667
668
669
670
671
672
673
        // Don't notify if the terminal is active
        bool hasFocus = false;
        for (TerminalDisplay *display : _views) {
            if (display->hasFocus()) {
                hasFocus = true;
                break;
            }
        }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
674
        if (_monitorActivity  && !_notifiedActivity) {
675
676
            KNotification::event(hasFocus ? "Activity" : "ActivityHidden",
                                 i18n("Activity in session '%1'", _nameTitle), QPixmap(),
Kurt Hindenburg's avatar
Kurt Hindenburg committed
677
678
                                 QApplication::activeWindow(),
                                 KNotification::CloseWhenWidgetActivated);
679
680
681

            // mask activity notification for a while to avoid flooding
            _notifiedActivity = true;
682
            _activityTimer->start(activityMaskInSeconds * 1000);
Jekyll Wu's avatar
Jekyll Wu committed
683
        }
684

685
        // reset the counter for monitoring continuous silence since there is activity
Kurt Hindenburg's avatar
Kurt Hindenburg committed
686
687
        if (_monitorSilence) {
            _silenceTimer->start(_silenceSeconds * 1000);
Jekyll Wu's avatar
Jekyll Wu committed
688
        }
689
    }
690

Kurt Hindenburg's avatar
Kurt Hindenburg committed
691
    if (state == NOTIFYACTIVITY && !_monitorActivity)
Jekyll Wu's avatar
Jekyll Wu committed
692
        state = NOTIFYNORMAL;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
693
    if (state == NOTIFYSILENCE && !_monitorSilence)
Jekyll Wu's avatar
Jekyll Wu committed
694
        state = NOTIFYNORMAL;
695

Jekyll Wu's avatar
Jekyll Wu committed
696
    emit stateChanged(state);
697
}
698

699
void Session::onViewSizeChange(int /*height*/, int /*width*/)
700
{
Jekyll Wu's avatar
Jekyll Wu committed
701
    updateTerminalSize();
702
703
}

704
void Session::updateTerminalSize()
705
706
707
708
{
    int minLines = -1;
    int minColumns = -1;

709
    // minimum number of lines and columns that views require for
710
711
712
713
714
    // 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;

715
    //select largest number of lines and columns that will fit in all visible views
Kurt Hindenburg's avatar
Kurt Hindenburg committed
716
    foreach(TerminalDisplay* view, _views) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
717
718
719
720
721
        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());
722
            view->processFilters();
723
        }
724
    }
725

726
    // backend emulation must have a _terminal of at least 1 column x 1 line in size
Kurt Hindenburg's avatar
Kurt Hindenburg committed
727
728
    if (minLines > 0 && minColumns > 0) {
        _emulation->setImageSize(minLines , minColumns);
729
730
    }
}
731
732
733
void Session::updateWindowSize(int lines, int columns)
{
    Q_ASSERT(lines > 0 && columns > 0);
734
    _shellProcess->setWindowSize(columns, lines);
735
}
736
737
738
739
740
741
742
743
744
745
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
746
    // in the window size (some shells do nothing if the
747
748
    // new and old sizes are the same)
    //
749
    // if there is a more 'correct' way to do this, please
750
751
752
    // send an email with method or patches to konsole-devel@kde.org

    const QSize existingSize = _shellProcess->windowSize();
753
    _shellProcess->setWindowSize(existingSize.width() + 1, existingSize.height());
754
    usleep(500); // introduce small delay to avoid changing size too quickly
755
    _shellProcess->setWindowSize(existingSize.width(), existingSize.height());
756
757
}

758
759
760
761
762
763
764
765
766
767
768
769
void Session::sendSignal(int signal)
{
    const ProcessInfo* process = getProcessInfo();
    bool ok = false;
    int pid;
    pid = process->foregroundPid(&ok);

    if (ok) {
        ::kill(pid, signal);
    }
}

770
771
772
773
void Session::reportBackgroundColor(const QColor& c)
{
    #define to65k(a) (QString("%1").arg((int)(a*0xFFFF), 4, 16, QChar('0')))
    QString msg = "\033]11;rgb:"
Kurt Hindenburg's avatar
Kurt Hindenburg committed
774
775
776
                + to65k(c.redF())   + '/'
                + to65k(c.greenF()) + '/'
                + to65k(c.blueF())  + '\a';
777
778
779
780
    _emulation->sendString(msg.toUtf8());
    #undef to65k
}

781
bool Session::kill(int signal)
782
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
783
    if (_shellProcess->pid() <= 0)
784
        return false;
785

Kurt Hindenburg's avatar
Kurt Hindenburg committed
786
    int result = ::kill(_shellProcess->pid(), signal);
787

Kurt Hindenburg's avatar
Kurt Hindenburg committed
788
789
    if (result == 0) {
        if (_shellProcess->waitForFinished(1000))
790
791
792
            return true;
        else
            return false;
793
    } else {
794
        return false;
795
    }
796
797
}

798
void Session::close()
799
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
800
801
    if (isRunning()) {
        if (!closeInNormalWay())
802
            closeInForceWay();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
803
    } else {
804
805
806
807
808
809
810
811
        // terminal process has finished, just close the session
        QTimer::singleShot(1, this, SIGNAL(finished()));
    }
}

bool Session::closeInNormalWay()
{
    _autoClose    = true;
812
    _closePerUserRequest = true;
813

814
815
816
817
818
819
    // 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
820
    if (!isRunning()) {
821
822
823
824
        emit finished();
        return true;
    }

825
826
827
828
829
830
831
832
833
    _shellProcess->sendEof();

    if (_shellProcess->waitForFinished(1000)) {
        return true;
    }

    qWarning() << "shell did not close, sending SIGHUP";

    // We tried asking nicely, ask a bit less nicely
Kurt Hindenburg's avatar
Kurt Hindenburg committed
834
    if (kill(SIGHUP)) {
835
        return true;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
836
    } else {
Laurent Montel's avatar
Laurent Montel committed
837
        qWarning() << "Process " << _shellProcess->pid() << " did not die with SIGHUP";
838
        _shellProcess->closePty();
839
        return (_shellProcess->waitForFinished(1000));
840
841
    }
}
842

843
844
845
bool Session::closeInForceWay()
{
    _autoClose    = true;
846
    _closePerUserRequest = true;
847

Kurt Hindenburg's avatar
Kurt Hindenburg committed
848
    if (kill(SIGKILL)) {
849
        return true;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
850
    } else {
Laurent Montel's avatar
Laurent Montel committed
851
        qWarning() << "Process " << _shellProcess->pid() << " did not die with SIGKILL";
852
        return false;
Jekyll Wu's avatar
Jekyll Wu committed
853
    }
854
855
}

856
void Session::sendTextToTerminal(const QString& text, const QChar& eol) const
857
{
858
    _emulation->sendText(text + eol);
859
860
}

861
// Only D-Bus calls this function (via SendText or runCommand)
862
void Session::sendText(const QString& text) const
863
{
864
865
866
867
868
869
870
871
872
873
#if not defined(REMOVE_SENDTEXT_RUNCOMMAND_DBUS_METHODS)
    if (show_disallow_certain_dbus_methods_message) {

        KNotification::event(KNotification::Warning, "Konsole D-Bus Warning",
            i18n("The D-Bus methods sendText/runCommand were just used.  There are security concerns about allowing these methods to be public.  If desired, these methods can be changed to internal use only by re-compiling Konsole. <p>This warning will only show once for this Konsole instance.</p>"));

        show_disallow_certain_dbus_methods_message = false;
    }
#endif

Jekyll Wu's avatar
Jekyll Wu committed
874
    _emulation->sendText(text);
875
876
}

877
// Only D-Bus calls this function
878
879
void Session::runCommand(const QString& command) const
{
880
    sendText(command + '\n');
881
882
}

883
884
885
886
887
void Session::sendMouseEvent(int buttons, int column, int line, int eventType)
{
    _emulation->sendMouseEvent(buttons, column, line, eventType);
}

888
void Session::done(int exitCode, QProcess::ExitStatus exitStatus)
889
{
890
    // This slot should be triggered only one time
891
892
    disconnect(_shellProcess, static_cast<void(Pty::*)(int,QProcess::ExitStatus)>(&Konsole::Pty::finished),
               this, &Konsole::Session::done);
893

Kurt Hindenburg's avatar
Kurt Hindenburg committed
894
    if (!_autoClose) {
Jekyll Wu's avatar
Jekyll Wu committed
895
896
897
898
        _userTitle = i18nc("@info:shell This session is done", "Finished");
        emit titleChanged();
        return;
    }
Robert Knight's avatar
Robert Knight committed
899

Kurt Hindenburg's avatar
Kurt Hindenburg committed
900
    if (_closePerUserRequest) {
901
902
903
904
        emit finished();
        return;
    }

Jekyll Wu's avatar
Jekyll Wu committed
905
    QString message;