Session.cpp 50 KB
Newer Older
1
/*
2
3
4
    SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
    SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
    SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
5

6
    SPDX-License-Identifier: GPL-2.0-or-later
7
*/
8

9
// Own
10
#include "Session.h"
11

12
// Standard
Kurt Hindenburg's avatar
Kurt Hindenburg committed
13
#include <csignal>
14
#include <cstdlib>
15
#include <unistd.h>
16

17
// Qt
18
#include <QApplication>
19
20
21
#include <QColor>
#include <QDir>
#include <QFile>
22
#include <QKeyEvent>
23

24
// KDE
25
26
#include <KConfigGroup>
#include <KIO/DesktopExecParser>
27
#include <KLocalizedString>
28
#include <KNotification>
29
#include <KProcess>
30
#include <KShell>
31
#include <kcoreaddons_version.h>
32

33
// Konsole
34
35
#include <sessionadaptor.h>

36
#include "Pty.h"
37
38
#include "SSHProcessInfo.h"
#include "SessionManager.h"
39
#include "ShellCommand.h"
40
#include "Vt102Emulation.h"
41
#include "ZModemDialog.h"
42
#include "history/HistoryTypeFile.h"
43
#include "history/HistoryTypeNone.h"
44
#include "history/compact/CompactHistoryType.h"
45
#include "konsoledebug.h"
46
47
#include "profile/Profile.h"
#include "profile/ProfileManager.h"
48
49

#include "terminalDisplay/TerminalDisplay.h"
50
#include "terminalDisplay/TerminalScrollBar.h"
51

52
// Linux
53
#ifdef HAVE_GETPWUID
54
#include <pwd.h>
55
#include <sys/types.h>
56
57
#endif

58
using namespace Konsole;
59

60
int Session::lastSessionId = 0;
61
static bool show_disallow_certain_dbus_methods_message = true;
62

63
64
static const int ZMODEM_BUFFER_SIZE = 1048576; // 1 Mb

65
66
Session::Session(QObject *parent)
    : QObject(parent)
67
    , _uniqueIdentifier(QUuid())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
68
69
    , _shellProcess(nullptr)
    , _emulation(nullptr)
70
    , _views(QList<TerminalDisplay *>())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
71
72
73
74
    , _monitorActivity(false)
    , _monitorSilence(false)
    , _notifiedActivity(false)
    , _silenceSeconds(10)
75
76
    , _silenceTimer(nullptr)
    , _activityTimer(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
77
78
    , _autoClose(true)
    , _closePerUserRequest(false)
79
80
81
82
83
    , _nameTitle(QString())
    , _displayTitle(QString())
    , _userTitle(QString())
    , _localTabTitleFormat(QString())
    , _remoteTabTitleFormat(QString())
84
    , _tabTitleSetByUser(false)
85
    , _tabColorSetByUser(false)
86
87
    , _iconName(QString())
    , _iconText(QString())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
88
    , _addToUtmp(true)
Jekyll Wu's avatar
Jekyll Wu committed
89
    , _flowControlEnabled(true)
90
91
92
    , _program(QString())
    , _arguments(QStringList())
    , _environment(QStringList())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
93
    , _sessionId(0)
94
95
96
    , _initialWorkingDir(QString())
    , _currentWorkingDir(QString())
    , _reportedWorkingUrl(QUrl())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
97
98
    , _sessionProcessInfo(nullptr)
    , _foregroundProcessInfo(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
99
100
    , _foregroundPid(0)
    , _zmodemBusy(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
101
102
    , _zmodemProc(nullptr)
    , _zmodemProgress(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
103
    , _hasDarkBackground(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
104
105
    , _preferredSize(QSize())
    , _readOnly(false)
106
    , _isPrimaryScreen(true)
Robert Knight's avatar
   
Robert Knight committed
107
{
108
    _uniqueIdentifier = QUuid::createUuid();
109

110
    // prepare DBus communication
111
    new SessionAdaptor(this);
112
    _sessionId = ++lastSessionId;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
113
    QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/") + QString::number(_sessionId), this);
Robert Knight's avatar
   
Robert Knight committed
114

115
    // create emulation backend
116
    _emulation = new Vt102Emulation();
117
    _emulation->reset();
Robert Knight's avatar
   
Robert Knight committed
118

119
    connect(_emulation, &Konsole::Emulation::sessionAttributeChanged, this, &Konsole::Session::setSessionAttribute);
120
    connect(_emulation, &Konsole::Emulation::bell, this, [this]() {
121
        Q_EMIT bellRequest(i18n("Bell in '%1' (Session '%2')", _displayTitle, _nameTitle));
122
123
        this->setPendingNotification(Notification::Bell);
    });
124
125
    connect(_emulation, &Konsole::Emulation::zmodemDownloadDetected, this, &Konsole::Session::fireZModemDownloadDetected);
    connect(_emulation, &Konsole::Emulation::zmodemUploadDetected, this, &Konsole::Session::fireZModemUploadDetected);
Laurent Montel's avatar
Laurent Montel committed
126
127
128
129
130
    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);
131
    connect(_emulation, &Konsole::Emulation::sessionAttributeRequest, this, &Konsole::Session::sessionAttributeRequest);
Robert Knight's avatar
   
Robert Knight committed
132

133
    // create new teletype for I/O with shell process
134
    openTeletype(-1, true);
135

136
    // setup timer for monitoring session activity & silence
137
138
    _silenceTimer = new QTimer(this);
    _silenceTimer->setSingleShot(true);
139
    connect(_silenceTimer, &QTimer::timeout, this, &Konsole::Session::silenceTimerDone);
140

Kurt Hindenburg's avatar
Kurt Hindenburg committed
141
    _activityTimer = new QTimer(this);
142
    _activityTimer->setSingleShot(true);
143
    connect(_activityTimer, &QTimer::timeout, this, &Konsole::Session::activityTimerDone);
144
145
}

Jekyll Wu's avatar
Jekyll Wu committed
146
147
148
149
150
151
152
153
154
Session::~Session()
{
    delete _foregroundProcessInfo;
    delete _sessionProcessInfo;
    delete _emulation;
    delete _shellProcess;
    delete _zmodemProc;
}

155
void Session::openTeletype(int fd, bool runShell)
156
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
157
    if (isRunning()) {
Laurent Montel's avatar
Laurent Montel committed
158
        qWarning() << "Attempted to open teletype in a running session.";
159
160
        return;
    }
161

162
    delete _shellProcess;
163

Kurt Hindenburg's avatar
Kurt Hindenburg committed
164
    if (fd < 0) {
165
        _shellProcess = new Pty();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
166
    } else {
167
        _shellProcess = new Pty(fd);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
168
    }
169

170
    _shellProcess->setUtf8Mode(_emulation->utf8());
171

172
    // connect the I/O between emulator and pty process
Laurent Montel's avatar
Laurent Montel committed
173
174
    connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock);
    connect(_emulation, &Konsole::Emulation::sendData, _shellProcess, &Konsole::Pty::sendData);
175
176

    // UTF8 mode
Laurent Montel's avatar
Laurent Montel committed
177
    connect(_emulation, &Konsole::Emulation::useUtf8Request, _shellProcess, &Konsole::Pty::setUtf8Mode);
178
179

    // get notified when the pty process is finished
180
    connect(_shellProcess, QOverload<int, QProcess::ExitStatus>::of(&Konsole::Pty::finished), this, &Konsole::Session::done);
181
182

    // emulator size
183
    connect(_emulation, &Konsole::Emulation::imageSizeChanged, this, &Konsole::Session::updateWindowSize);
184
    if (fd < 0 || runShell) {
185
186
187
188
        // Using a queued connection guarantees that starting the session
        // is delayed until all (both) image size updates at startup have
        // been processed. See #203185 and #412598.
        connect(_emulation, &Konsole::Emulation::imageSizeInitialized, this, &Konsole::Session::run, Qt::QueuedConnection);
189
190
191
192
    } else {
        // run needs to be disconnected, as it may be already connected by the constructor
        disconnect(_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
213
214
215
216
217
218
        /**
         * compute the windows id to use
         * doesn't call winId on some widget, as this might lead
         * to rendering artifacts as this will trigger the
         * creation of a native window, see https://doc.qt.io/qt-5/qwidget.html#winId
         * instead, use https://doc.qt.io/qt-5/qwidget.html#effectiveWinId
         */
219
        QWidget *widget = _views.first();
220
221
        Q_ASSERT(widget);
        return widget->effectiveWinId();
222
223
224
    }
}

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

230
bool Session::isRunning() const
231
{
232
    return (_shellProcess != nullptr) && (_shellProcess->state() == QProcess::Running);
233
234
}

235
236
bool Session::hasFocus() const
{
237
238
239
    return std::any_of(_views.constBegin(), _views.constEnd(), [](const TerminalDisplay *display) {
        return display->hasFocus();
    });
240
241
}

242
void Session::setCodec(QTextCodec *codec)
243
{
244
245
246
247
    if (isReadOnly()) {
        return;
    }

248
    emulation()->setCodec(codec);
249

Ahmad Samir's avatar
Ahmad Samir committed
250
    Q_EMIT sessionCodecChanged(codec);
251
252
}

253
bool Session::setCodec(const QByteArray &name)
254
{
255
    QTextCodec *codec = QTextCodec::codecForName(name);
256

257
    if (codec != nullptr) {
258
259
        setCodec(codec);
        return true;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
260
    } else {
261
262
        return false;
    }
263
264
265
266
267
268
269
}

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

270
void Session::setProgram(const QString &program)
Robert Knight's avatar
   
Robert Knight committed
271
{
272
273
    _program = ShellCommand::expand(program);
}
Jekyll Wu's avatar
Jekyll Wu committed
274

275
void Session::setArguments(const QStringList &arguments)
Robert Knight's avatar
   
Robert Knight committed
276
{
277
    _arguments = ShellCommand::expand(arguments);
Robert Knight's avatar
   
Robert Knight committed
278
279
}

280
void Session::setInitialWorkingDirectory(const QString &dir)
Jekyll Wu's avatar
Jekyll Wu committed
281
{
282
    _initialWorkingDir = validDirectory(KShell::tildeExpand(ShellCommand::expand(dir)));
Jekyll Wu's avatar
Jekyll Wu committed
283
284
}

Robert Knight's avatar
   
Robert Knight committed
285
286
QString Session::currentWorkingDirectory()
{
287
288
289
290
    if (_reportedWorkingUrl.isValid() && _reportedWorkingUrl.isLocalFile()) {
        return _reportedWorkingUrl.path();
    }

Robert Knight's avatar
   
Robert Knight committed
291
    // only returned cached value
Kurt Hindenburg's avatar
Kurt Hindenburg committed
292
    if (_currentWorkingDir.isEmpty()) {
293
        updateWorkingDirectory();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
294
    }
295

Robert Knight's avatar
   
Robert Knight committed
296
297
    return _currentWorkingDir;
}
298
void Session::updateWorkingDirectory()
Robert Knight's avatar
   
Robert Knight committed
299
{
300
    updateSessionProcessInfo();
301

302
    const QString currentDir = _sessionProcessInfo->validCurrentDir();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
303
    if (currentDir != _currentWorkingDir) {
304
        _currentWorkingDir = currentDir;
Ahmad Samir's avatar
Ahmad Samir committed
305
        Q_EMIT currentDirectoryChanged(_currentWorkingDir);
306
    }
Robert Knight's avatar
   
Robert Knight committed
307
308
}

309
QList<TerminalDisplay *> Session::views() const
310
311
312
313
{
    return _views;
}

314
void Session::addView(TerminalDisplay *widget)
315
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
316
    Q_ASSERT(!_views.contains(widget));
317
318
319

    _views.append(widget);

320
    // connect emulation - view signals and slots
Laurent Montel's avatar
Laurent Montel committed
321
322
323
    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);
324
    connect(widget, &Konsole::TerminalDisplay::peekPrimaryRequested, _emulation, &Konsole::Emulation::setPeekPrimary);
325

326
327
328
    // allow emulation to notify the view when the foreground process
    // indicates whether or not it is interested in Mouse Tracking events
    connect(_emulation, &Konsole::Emulation::programRequestsMouseTracking, widget, &Konsole::TerminalDisplay::setUsesMouseTracking);
329

330
    widget->setUsesMouseTracking(_emulation->programUsesMouseTracking());
331

332
    connect(_emulation, &Konsole::Emulation::enableAlternateScrolling, widget->scrollBar(), &Konsole::TerminalScrollBar::setAlternateScrolling);
333

Laurent Montel's avatar
Laurent Montel committed
334
    connect(_emulation, &Konsole::Emulation::programBracketedPasteModeChanged, widget, &Konsole::TerminalDisplay::setBracketedPasteMode);
335
336
337

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

338
    widget->setScreenWindow(_emulation->createWindow());
Robert Knight's avatar
   
Robert Knight committed
339

340
341
    _emulation->setCurrentTerminalDisplay(widget);

342
    // connect view signals and slots
Laurent Montel's avatar
Laurent Montel committed
343
    connect(widget, &Konsole::TerminalDisplay::changedContentSizeSignal, this, &Konsole::Session::onViewSizeChange);
344

Laurent Montel's avatar
Laurent Montel committed
345
    connect(widget, &Konsole::TerminalDisplay::destroyed, this, &Konsole::Session::viewDestroyed);
346

347
    connect(widget, &Konsole::TerminalDisplay::compositeFocusChanged, _emulation, &Konsole::Emulation::focusChanged);
348
349
350

    connect(_emulation, &Konsole::Emulation::setCursorStyleRequest, widget, &Konsole::TerminalDisplay::setCursorStyle);
    connect(_emulation, &Konsole::Emulation::resetCursorStyleRequest, widget, &Konsole::TerminalDisplay::resetCursorStyle);
351
352

    connect(widget, &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::Session::resetNotifications);
353
354
}

355
void Session::viewDestroyed(QObject *view)
356
{
357
    auto *display = reinterpret_cast<TerminalDisplay *>(view);
358

Kurt Hindenburg's avatar
Kurt Hindenburg committed
359
    Q_ASSERT(_views.contains(display));
360

361
    removeView(display);
362
363
}

364
void Session::removeView(TerminalDisplay *widget)
365
366
367
{
    _views.removeAll(widget);

Kurt Hindenburg's avatar
Kurt Hindenburg committed
368
    disconnect(widget, nullptr, this, nullptr);
369

370
371
372
373
374
375
    // disconnect
    //  - key presses signals from widget
    //  - mouse activity signals from widget
    //  - string sending signals from widget
    //
    //  ... and any other signals connected in addView()
Kurt Hindenburg's avatar
Kurt Hindenburg committed
376
    disconnect(widget, nullptr, _emulation, nullptr);
377
378

    // disconnect state change signals emitted by emulation
Kurt Hindenburg's avatar
Kurt Hindenburg committed
379
    disconnect(_emulation, nullptr, widget, nullptr);
380

381
    // close the session automatically when the last view is removed
Kurt Hindenburg's avatar
Kurt Hindenburg committed
382
    if (_views.count() == 0) {
383
384
        close();
    }
385
386
}

387
388
// Upon a KPty error, there is no description on what that error was...
// Check to see if the given program is executable.
389
QString Session::checkProgram(const QString &program)
390
{
391
    QString exec = program;
392

Kurt Hindenburg's avatar
Kurt Hindenburg committed
393
    if (exec.isEmpty()) {
Jekyll Wu's avatar
Jekyll Wu committed
394
        return QString();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
395
    }
396

397
398
399
400
401
    QFileInfo info(exec);
    if (info.isAbsolute() && info.exists() && info.isExecutable()) {
        return exec;
    }

402
    exec = KIO::DesktopExecParser::executablePath(exec);
Jekyll Wu's avatar
Jekyll Wu committed
403
    exec = KShell::tildeExpand(exec);
404
    const QString pexec = QStandardPaths::findExecutable(exec);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
405
    if (pexec.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
406
        qCritical() << i18n("Could not find binary: ") << exec;
Jekyll Wu's avatar
Jekyll Wu committed
407
408
        return QString();
    }
409

Jekyll Wu's avatar
Jekyll Wu committed
410
    return exec;
411
412
}

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

418
419
    static const char redPenOn[] = "\033[1m\033[31m";
    static const char redPenOff[] = "\033[0m";
420

421
    _emulation->receiveData(redPenOn, qstrlen(redPenOn));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
422
    _emulation->receiveData("\n\r\n\r", 4);
423
424
    _emulation->receiveData(warningText.constData(), qstrlen(warningText.constData()));
    _emulation->receiveData(messageText.constData(), qstrlen(messageText.constData()));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
425
    _emulation->receiveData("\n\r\n\r", 4);
426
    _emulation->receiveData(redPenOff, qstrlen(redPenOff));
427
}
428
429
430
431

QString Session::shellSessionId() const
{
    QString friendlyUuid(_uniqueIdentifier.toString());
432
    friendlyUuid.remove(QLatin1Char('-')).remove(QLatin1Char('{')).remove(QLatin1Char('}'));
433
434
435
436

    return friendlyUuid;
}

437
438
void Session::run()
{
439
    // FIXME: run() is called twice in some instances
Kurt Hindenburg's avatar
Kurt Hindenburg committed
440
    if (isRunning()) {
441
        qCDebug(KonsoleDebug) << "Attempted to re-run an already running session (" << processId() << ")";
442
443
444
        return;
    }

445
    // check that everything is in place to run the session
Kurt Hindenburg's avatar
Kurt Hindenburg committed
446
    if (_program.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
447
        qWarning() << "Program to run not set.";
Jekyll Wu's avatar
Jekyll Wu committed
448
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
449
    if (_arguments.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
450
        qWarning() << "No command line arguments specified.";
Jekyll Wu's avatar
Jekyll Wu committed
451
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
452
    if (_uniqueIdentifier.isNull()) {
453
        _uniqueIdentifier = QUuid::createUuid();
Jekyll Wu's avatar
Jekyll Wu committed
454
455
    }

456
457
    QStringList programs = {_program, QString::fromUtf8(qgetenv("SHELL")), QStringLiteral("/bin/sh")};

458
#ifdef HAVE_GETPWUID
459
460
    auto pw = getpwuid(getuid());
    // pw may be NULL
461
    if (pw != nullptr) {
462
463
        programs.insert(1, QString::fromLocal8Bit(pw->pw_shell));
    }
464
    // pw: Do not pass the returned pointer to free.
465
466
#endif

Jekyll Wu's avatar
Jekyll Wu committed
467
    QString exec;
468
    for (const auto &choice : programs) {
469
470
        exec = checkProgram(choice);
        if (!exec.isEmpty()) {
Jekyll Wu's avatar
Jekyll Wu committed
471
            break;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
472
        }
Jekyll Wu's avatar
Jekyll Wu committed
473
474
    }

Adriaan de Groot's avatar
Adriaan de Groot committed
475
476
    // if nothing could be found (not even the fallbacks), print a warning and do not run
    if (exec.isEmpty()) {
Jekyll Wu's avatar
Jekyll Wu committed
477
478
479
480
        terminalWarning(i18n("Could not find an interactive shell to start."));
        return;
    }

Adriaan de Groot's avatar
Adriaan de Groot committed
481
482
483
    // if a program was specified via setProgram(), but it couldn't be found (but a fallback was), print a warning
    if (exec != checkProgram(_program)) {
        terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.", _program, exec));
484
    } else if (exec != checkProgram(exec)) {
Adriaan de Groot's avatar
Adriaan de Groot committed
485
486
487
        terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.", exec, checkProgram(exec)));
    }

Jekyll Wu's avatar
Jekyll Wu committed
488
    // if no arguments are specified, fall back to program name
489
    QStringList arguments = _arguments.join(QLatin1Char(' ')).isEmpty() ? QStringList() << exec : _arguments;
Jekyll Wu's avatar
Jekyll Wu committed
490

491
    if (!_initialWorkingDir.isEmpty()) {
492
        _shellProcess->setInitialWorkingDirectory(_initialWorkingDir);
493
494
495
    } else {
        _shellProcess->setInitialWorkingDirectory(QDir::currentPath());
    }
Jekyll Wu's avatar
Jekyll Wu committed
496

Jekyll Wu's avatar
Jekyll Wu committed
497
    _shellProcess->setFlowControlEnabled(_flowControlEnabled);
498
    _shellProcess->setEraseChar(_emulation->eraseChar());
499
    _shellProcess->setUseUtmp(_addToUtmp);
Jekyll Wu's avatar
Jekyll Wu committed
500
501
502
503
504

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

508
    addEnvironmentEntry(QStringLiteral("SHELL_SESSION_ID=%1").arg(shellSessionId()));
Jekyll Wu's avatar
Jekyll Wu committed
509

510
    addEnvironmentEntry(QStringLiteral("WINDOWID=%1").arg(QString::number(windowId())));
511
512

    const QString dbusService = QDBusConnection::sessionBus().baseService();
513
    addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_SERVICE=%1").arg(dbusService));
514

515
516
    const QString dbusObject = QStringLiteral("/Sessions/%1").arg(QString::number(_sessionId));
    addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_SESSION=%1").arg(dbusObject));
517

518
    int result = _shellProcess->start(exec, arguments, _environment);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
519
    if (result < 0) {
520
        terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec, arguments.join(QLatin1String(" "))));
521
        terminalWarning(_shellProcess->errorString());
Jekyll Wu's avatar
Jekyll Wu committed
522
523
524
        return;
    }

525
    _shellProcess->setWriteable(false); // We are reachable via kwrited.
Jekyll Wu's avatar
Jekyll Wu committed
526

Ahmad Samir's avatar
Ahmad Samir committed
527
    Q_EMIT started();
528
529
}

530
void Session::setSessionAttribute(int what, const QString &caption)
531
{
532
533
    // set to true if anything has actually changed
    // eg. old _nameTitle != new _nameTitle
534
    bool modified = false;
535

Kurt Hindenburg's avatar
Kurt Hindenburg committed
536
537
    if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) {
        if (_userTitle != caption) {
538
539
540
            _userTitle = caption;
            modified = true;
        }
541
    }
542

Kurt Hindenburg's avatar
Kurt Hindenburg committed
543
544
    if ((what == IconNameAndWindowTitle) || (what == IconName)) {
        if (_iconText != caption) {
Jekyll Wu's avatar
Jekyll Wu committed
545
            _iconText = caption;
546
547
548
            modified = true;
        }
    }
549

Kurt Hindenburg's avatar
Kurt Hindenburg committed
550
    if (what == TextColor || what == BackgroundColor) {
551
        QString colorString = caption.section(QLatin1Char(';'), 0, 0);
Jekyll Wu's avatar
Jekyll Wu committed
552
        QColor color = QColor(colorString);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
553
        if (color.isValid()) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
554
            if (what == TextColor) {
Ahmad Samir's avatar
Ahmad Samir committed
555
                Q_EMIT changeForegroundColorRequest(color);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
556
            } else {
Ahmad Samir's avatar
Ahmad Samir committed
557
                Q_EMIT changeBackgroundColorRequest(color);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
558
            }
Jekyll Wu's avatar
Jekyll Wu committed
559
        }
560
    }
561

Kurt Hindenburg's avatar
Kurt Hindenburg committed
562
    if (what == SessionName) {
563
564
565
566
        if (_localTabTitleFormat != caption) {
            _localTabTitleFormat = caption;
            setTitle(Session::DisplayedTitleRole, caption);
            modified = true;
567
568
        }
    }
569

570
571
572
    /* 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
573
    // change icon via \033]32;Icon\007
574
    if (what == SessionIcon) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
575
        if (_iconName != caption) {
Jekyll Wu's avatar
Jekyll Wu committed
576
            _iconName = caption;
577

578
579
            modified = true;
        }
580
    }
581

582
583
    if (what == CurrentDirectory) {
        _reportedWorkingUrl = QUrl::fromUserInput(caption);
Ahmad Samir's avatar
Ahmad Samir committed
584
        Q_EMIT currentDirectoryChanged(currentWorkingDirectory());
585
586
587
        modified = true;
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
588
    if (what == ProfileChange) {
Ahmad Samir's avatar
Ahmad Samir committed
589
        Q_EMIT profileChangeCommandReceived(caption);
590
591
592
        return;
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
593
    if (modified) {
Ahmad Samir's avatar
Ahmad Samir committed
594
        Q_EMIT sessionAttributeChanged();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
595
    }
596
597
}

598
QString Session::userTitle() const
Robert Knight's avatar
Robert Knight committed
599
600
601
{
    return _userTitle;
}
602

603
void Session::setTabTitleFormat(TabTitleContext context, const QString &format)
604
{
605
    if (context == LocalTabTitle) {
606
        _localTabTitleFormat = format;
607
        ProcessInfo *process = getProcessInfo();
608
609
        process->setUserNameRequired(format.contains(QLatin1String("%u")));
    } else if (context == RemoteTabTitle) {
610
        _remoteTabTitleFormat = format;
611
    }
612
}
613

614
QString Session::tabTitleFormat(TabTitleContext context) const
Robert Knight's avatar
Robert Knight committed
615
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
616
    if (context == LocalTabTitle) {
617
        return _localTabTitleFormat;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
618
    } else if (context == RemoteTabTitle) {
619
        return _remoteTabTitleFormat;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
620
    }
621

622
    return QString();
Robert Knight's avatar
Robert Knight committed
623
624
}

625
626
627
628
629
630
631
632
633
634
void Session::tabTitleSetByUser(bool set)
{
    _tabTitleSetByUser = set;
}

bool Session::isTabTitleSetByUser() const
{
    return _tabTitleSetByUser;
}

635
636
637
638
639
640
641
642
643
644
void Session::tabColorSetByUser(bool set)
{
    _tabColorSetByUser = set;
}

bool Session::isTabColorSetByUser() const
{
    return _tabColorSetByUser;
}

645
void Session::silenceTimerDone()
646
{
647
648
    // 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.
Jekyll Wu's avatar
Jekyll Wu committed
649
    //
650
651
    // 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
Jekyll Wu's avatar
Jekyll Wu committed
652

653
    // FIXME: Make message text for this notification and the activity notification more descriptive.
654
    if (!_monitorSilence) {
655
        setPendingNotification(Notification::Silence, false);
656
657
658
        return;
    }

659
    KNotification::event(hasFocus() ? QStringLiteral("Silence") : QStringLiteral("SilenceHidden"),
660
661
662
663
                         i18n("Silence in '%1' (Session '%2')", _displayTitle, _nameTitle),
                         QPixmap(),
                         QApplication::activeWindow(),
                         KNotification::CloseWhenWidgetActivated);
664
    setPendingNotification(Notification::Silence);
665
}
666
667
668
669
670
671

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

672
673
674
void Session::resetNotifications()
{
    static const Notification availableNotifications[] = {Activity, Silence, Bell};
675
    for (auto notification : availableNotifications) {
676
677
678
679
        setPendingNotification(notification, false);
    }
}

Robert Knight's avatar
   
Robert Knight committed
680
681
void Session::updateFlowControlState(bool suspended)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
682
683
    if (suspended) {
        if (flowControlEnabled()) {
684
            for (TerminalDisplay *display : qAsConst(_views)) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
685
                if (display->flowControlWarningEnabled()) {
Robert Knight's avatar
   
Robert Knight committed
686
                    display->outputSuspended(true);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
687
                }
Robert Knight's avatar
   
Robert Knight committed
688
689
            }
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
690
    } else {
691
        for (TerminalDisplay *display : qAsConst(_views)) {
Jekyll Wu's avatar
Jekyll Wu committed
692
693
            display->outputSuspended(false);
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
694
    }
Robert Knight's avatar
   
Robert Knight committed
695
}
696
697
698

void Session::onPrimaryScreenInUse(bool use)
{
699
    _isPrimaryScreen = use;
Ahmad Samir's avatar
Ahmad Samir committed
700
    Q_EMIT primaryScreenInUse(use);
701
}
702

703
704
705
706
bool Session::isPrimaryScreen()
{
    return _isPrimaryScreen;
}
707

708
void Session::sessionAttributeRequest(int id, uint terminator)
709
710
{
    switch (id) {
711
712
713
714
715
716
717
718
    case TextColor:
        // Get 'TerminalDisplay' (_view) foreground color
        Q_EMIT getForegroundColor(terminator);
        break;
    case BackgroundColor:
        // Get 'TerminalDisplay' (_view) background color
        Q_EMIT getBackgroundColor(terminator);
        break;
719
720
721
    }
}

722
void Session::onViewSizeChange(int /*height*/, int /*width*/)
723
{
Jekyll Wu's avatar
Jekyll Wu committed
724
    updateTerminalSize();
725
726
}

727
void Session::updateTerminalSize()
728
729
730
731
{
    int minLines = -1;
    int minColumns = -1;

732
    // minimum number of lines and columns that views require for
733
734
735
736
737
    // 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;

738
    // select largest number of lines and columns that will fit in all visible views
739
    for (TerminalDisplay *view : qAsConst(_views)) {
740
741
742
        if (!view->isHidden() && 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());
743
            view->processFilters();
744
        }
745
    }
746

747
    // backend emulation must have a _terminal of at least 1 column x 1 line in size
Kurt Hindenburg's avatar
Kurt Hindenburg committed
748
    if (minLines > 0 && minColumns > 0) {
749
        _emulation->setImageSize(minLines, minColumns);
750
751
    }
}
752
753
754
void Session::updateWindowSize(int lines, int columns)
{
    Q_ASSERT(lines > 0 && columns > 0);
755
    _shellProcess->setWindowSi