Session.cpp 51 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
14
#include <cstdlib>
#include <csignal>
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
#include <KLocalizedString>
26
#include <KNotification>
27
#include <KShell>
28
29
#include <KProcess>
#include <KConfigGroup>
30
#include <KIO/DesktopExecParser>
31
#include <kcoreaddons_version.h>
32

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

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

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

52
// Linux
53
#ifdef HAVE_GETPWUID
54
55
56
57
#include <sys/types.h>
#include <pwd.h>
#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
Session::Session(QObject* parent) :
Kurt Hindenburg's avatar
Kurt Hindenburg committed
66
    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

Robert Knight's avatar
   
Robert Knight committed
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);
126
    connect(_emulation, &Konsole::Emulation::changeTabTextColorRequest, this, &Konsole::Session::changeTabTextColor);
Laurent Montel's avatar
Laurent Montel committed
127
128
129
130
131
    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);
132
    connect(_emulation, &Konsole::Emulation::sessionAttributeRequest, this, &Konsole::Session::sessionAttributeRequest);
Robert Knight's avatar
   
Robert Knight committed
133

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

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

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

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

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

163
    delete _shellProcess;
164

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

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

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

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

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

    // emulator size
186
    connect(_emulation, &Konsole::Emulation::imageSizeChanged, this, &Konsole::Session::updateWindowSize);
187
    if (fd < 0 || runShell) {
188
189
190
191
        // 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);
192
193
194
195
    } 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
196
197
}

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
212
    if (_views.count() == 0) {
Jekyll Wu's avatar
Jekyll Wu committed
213
        return 0;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
214
    } else {
215
216
217
218
219
220
221
222
223
224
        /**
         * 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
         */
        QWidget* widget = _views.first();
        Q_ASSERT(widget);
        return widget->effectiveWinId();
225
226
227
    }
}

228
229
230
231
void Session::setDarkBackground(bool darkBackground)
{
    _hasDarkBackground = darkBackground;
}
Jekyll Wu's avatar
Jekyll Wu committed
232

233
bool Session::isRunning() const
234
{
235
    return (_shellProcess != nullptr) && (_shellProcess->state() == QProcess::Running);
236
237
}

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

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

250
    emulation()->setCodec(codec);
251

Ahmad Samir's avatar
Ahmad Samir committed
252
    Q_EMIT sessionCodecChanged(codec);
253
254
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
255
bool Session::setCodec(const QByteArray& name)
256
{
257
    QTextCodec* codec = QTextCodec::codecForName(name);
258

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

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

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

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

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

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

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

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

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

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

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

    _views.append(widget);

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

328
329
330
    // 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);
331

332
    widget->setUsesMouseTracking(_emulation->programUsesMouseTracking());
333

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

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

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

340
    widget->setScreenWindow(_emulation->createWindow());
Robert Knight's avatar
   
Robert Knight committed
341

Robert Knight's avatar
   
Robert Knight committed
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
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
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
414
}

void Session::terminalWarning(const QString& message)
{
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;
    }

Jekyll Wu's avatar
Jekyll Wu committed
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
484
485
486
487
    // 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));
    } else if ( exec != checkProgram(exec)) {
        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() ?
Kurt Hindenburg's avatar
Kurt Hindenburg committed
490
491
                            QStringList() << exec :
                            _arguments;
Jekyll Wu's avatar
Jekyll Wu committed
492

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

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

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

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

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

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

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

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

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

Ahmad Samir's avatar
Ahmad Samir committed
529
    Q_EMIT started();
530
531
}

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

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

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

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

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

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

580
581
            modified = true;
        }
582
    }
583

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

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

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

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

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

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

624
    return QString();
Robert Knight's avatar
Robert Knight committed
625
626
}

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

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

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

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

647
void Session::silenceTimerDone()
648
{
Jekyll Wu's avatar
Jekyll Wu committed
649
650
651
652
653
654
    //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
655
    //FIXME: Make message text for this notification and the activity notification more descriptive.
656
    if (!_monitorSilence) {
657
        setPendingNotification(Notification::Silence, false);
658
659
660
        return;
    }

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

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

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

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

698
699
700
701
702
void Session::changeTabTextColor(int i)
{
    qCDebug(KonsoleDebug) << "Changing tab text color is not implemented "<<i;
}

703
704
void Session::onPrimaryScreenInUse(bool use)
{
705
    _isPrimaryScreen = use;
Ahmad Samir's avatar
Ahmad Samir committed
706
    Q_EMIT primaryScreenInUse(use);
707
}
708

709
710
711
712
bool Session::isPrimaryScreen()
{
    return _isPrimaryScreen;
}
713

714
void Session::sessionAttributeRequest(int id, uint terminator)
715
716
{
    switch (id) {
717
718
        case TextColor:
            // Get 'TerminalDisplay' (_view) foreground color
Ahmad Samir's avatar
Ahmad Samir committed
719
            Q_EMIT getForegroundColor(terminator);
720
            break;
721
722
        case BackgroundColor:
            // Get 'TerminalDisplay' (_view) background color
Ahmad Samir's avatar
Ahmad Samir committed
723
            Q_EMIT getBackgroundColor(terminator);
724
725
726
727
            break;
    }
}

728
void Session::onViewSizeChange(int /*height*/, int /*width*/)
729
{
Jekyll Wu's avatar
Jekyll Wu committed
730
    updateTerminalSize();
731
732
}

733
void Session::updateTerminalSize()
734
735
736
737
{
    int minLines = -1;
    int minColumns = -1;

738
    // minimum number of lines and columns that views require for
739
740
741
742
743
    // 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;

744
    //select largest number of lines and columns that will fit in all visible views
745
    for (TerminalDisplay *view : qAsConst(_views)) {
746
        if (!view->isHidden() &&
Kurt Hindenburg's avatar
Kurt Hindenburg committed
747
748
749
750
                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());
751
            view->processFilters();
752
        }
753
    }
754

755
    // backend emulation must have a _terminal of at least 1 column x 1 line in size
Kurt Hindenburg's avatar
Kurt Hindenburg committed
756
757
    if (minLines > 0 && minColumns > 0) {
        _emulation->setImageSize(minLines , minColumns);
758
759
    }
}
760
761
762
void Session::updateWindowSize(int lines, int columns)
{
    Q_ASSERT(lines > 0 && columns > 0);
763
    _shellProcess->setWindowSize(columns, lines);
764
}
765
766
767
768
769
770
771
772
773
774
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
775
    // in the window size (some shells do nothing if the
776
777
    // new and old sizes are the same)
    //
778
    // if there is a more 'correct' way to do this, please
779
780
781
    // send an email with method or patches to konsole-devel@kde.org

    const QSize existingSize = _shellProcess->windowSize();
782
    _shellProcess->setWindowSize(existingSize.width() + 1, existingSize.height());
783
784
    // introduce small delay to avoid changing size too quickly
    QThread::usleep(500);
785
    _shellProcess->setWindowSize(existingSize.width(), existingSize.height());
786
787
}

788
789
790
791
792
793
794
795
796
void Session::sendSignal(int signal)
{
    const ProcessInfo* process = getProcessInfo();
    bool ok = false;
    int pid;
    pid = process->foregroundPid(&ok);

    if (ok) {
        ::kill(pid, signal);
797
798
    } else {
        qWarning()<<"foreground process id not set, unable to send signal "<<signal;
799
800
801
    }
}

802
void Session::reportColor(SessionAttributes r, const QColor& c, uint terminator)
803
{
804
    #define to65k(a) (QStringLiteral("%1").arg(int(((a)*0xFFFF)), 4, 16, QLatin1Char('0')))
805
    QString msg = QStringLiteral("\033]%1;rgb:").arg(r)
806
807
                + to65k(c.redF())   + QLatin1Char('/')
                + to65k(c.greenF()) + QLatin1Char('/')
808
809
810
811
812