File indexing completed on 2024-04-28 05:50:47

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0003     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0004     SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
0005     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 // Own
0011 #include "Session.h"
0012 
0013 // Standard
0014 #include <csignal>
0015 #include <cstdlib>
0016 
0017 #ifndef Q_OS_WIN
0018 #include <unistd.h>
0019 #endif
0020 
0021 // Qt
0022 #include <QApplication>
0023 #include <QColor>
0024 #include <QDir>
0025 #include <QFile>
0026 #include <QKeyEvent>
0027 
0028 // KDE
0029 #include <KActionCollection>
0030 #include <KConfigGroup>
0031 #include <KIO/DesktopExecParser>
0032 #include <KLocalizedString>
0033 #include <KNotification>
0034 #include <KProcess>
0035 #include <KSelectAction>
0036 
0037 #ifndef Q_OS_WIN
0038 #include <KPtyDevice>
0039 #endif
0040 #include <KShell>
0041 
0042 #include <kcoreaddons_version.h>
0043 
0044 // Konsole
0045 #include <sessionadaptor.h>
0046 
0047 #include "Pty.h"
0048 #include "SSHProcessInfo.h"
0049 #include "SessionController.h"
0050 #include "SessionGroup.h"
0051 #include "SessionManager.h"
0052 #include "ShellCommand.h"
0053 #include "Vt102Emulation.h"
0054 #include "ZModemDialog.h"
0055 #include "decoders/PlainTextDecoder.h"
0056 #include "history/HistoryTypeFile.h"
0057 #include "history/HistoryTypeNone.h"
0058 #include "history/compact/CompactHistoryType.h"
0059 #include "konsoledebug.h"
0060 #include "profile/Profile.h"
0061 #include "profile/ProfileManager.h"
0062 
0063 #include "terminalDisplay/TerminalDisplay.h"
0064 #include "terminalDisplay/TerminalScrollBar.h"
0065 
0066 #ifndef Q_OS_WIN
0067 // Linux
0068 #if HAVE_GETPWUID
0069 #include <pwd.h>
0070 #include <sys/types.h>
0071 #endif
0072 
0073 #include <KSandbox>
0074 #endif // Q_OS_WIN
0075 
0076 using namespace Konsole;
0077 
0078 static bool show_disallow_certain_dbus_methods_message = true;
0079 
0080 static const int ZMODEM_BUFFER_SIZE = 1048576; // 1 Mb
0081 
0082 Session::Session(QObject *parent)
0083     : QObject(parent)
0084 {
0085     _uniqueIdentifier = QUuid::createUuid();
0086 
0087     // prepare DBus communication
0088     new SessionAdaptor(this);
0089 
0090     int maxSessionId = 0;
0091     auto allSessions = SessionManager::instance()->sessions();
0092     for (const auto &session : allSessions) {
0093         if (session->sessionId() > maxSessionId) {
0094             maxSessionId = session->sessionId();
0095         }
0096     }
0097     _sessionId = maxSessionId + 1;
0098     QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/") + QString::number(_sessionId), this);
0099 
0100     // create emulation backend
0101     _emulation = new Vt102Emulation();
0102     _emulation->reset();
0103 
0104     connect(_emulation, &Konsole::Emulation::sessionAttributeChanged, this, &Konsole::Session::setSessionAttribute);
0105     connect(_emulation, &Konsole::Emulation::bell, this, [this]() {
0106         Q_EMIT bellRequest(i18n("Bell in '%1' (Session '%2')", _displayTitle, _nameTitle));
0107         this->setPendingNotification(Notification::Bell);
0108     });
0109     connect(_emulation, &Konsole::Emulation::zmodemDownloadDetected, this, &Konsole::Session::fireZModemDownloadDetected);
0110     connect(_emulation, &Konsole::Emulation::zmodemUploadDetected, this, &Konsole::Session::fireZModemUploadDetected);
0111     connect(_emulation, &Konsole::Emulation::profileChangeCommandReceived, this, &Konsole::Session::profileChangeCommandReceived);
0112     connect(_emulation, &Konsole::Emulation::flowControlKeyPressed, this, &Konsole::Session::updateFlowControlState);
0113     connect(_emulation, &Konsole::Emulation::primaryScreenInUse, this, &Konsole::Session::onPrimaryScreenInUse);
0114     connect(_emulation, &Konsole::Emulation::selectionChanged, this, &Konsole::Session::selectionChanged);
0115     connect(_emulation, &Konsole::Emulation::imageResizeRequest, this, &Konsole::Session::resizeRequest);
0116     connect(_emulation, &Konsole::Emulation::sessionAttributeRequest, this, &Konsole::Session::sessionAttributeRequest);
0117 
0118     // create new teletype for I/O with shell process
0119     openTeletype(-1, true);
0120 
0121     // setup timer for monitoring session activity & silence
0122     _silenceTimer = new QTimer(this);
0123     _silenceTimer->setSingleShot(true);
0124     connect(_silenceTimer, &QTimer::timeout, this, &Konsole::Session::silenceTimerDone);
0125 
0126     _activityTimer = new QTimer(this);
0127     _activityTimer->setSingleShot(true);
0128     connect(_activityTimer, &QTimer::timeout, this, &Konsole::Session::activityTimerDone);
0129 }
0130 
0131 Session::~Session()
0132 {
0133     delete _foregroundProcessInfo;
0134     delete _sessionProcessInfo;
0135     delete _emulation;
0136     delete _shellProcess;
0137     delete _zmodemProc;
0138 }
0139 
0140 void Session::openTeletype(int fd, bool runShell)
0141 {
0142     if (isRunning()) {
0143         qWarning() << "Attempted to open teletype in a running session.";
0144         return;
0145     }
0146 
0147     delete _shellProcess;
0148 
0149     if (fd < 0) {
0150         _shellProcess = new Pty();
0151     } else {
0152         _shellProcess = new Pty(fd);
0153     }
0154 
0155     _shellProcess->setUtf8Mode(_emulation->utf8());
0156 
0157     // connect the I/O between emulator and pty process
0158     connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock);
0159     connect(_emulation, &Konsole::Emulation::sendData, _shellProcess, &Konsole::Pty::sendData);
0160 
0161     // UTF8 mode
0162     connect(_emulation, &Konsole::Emulation::useUtf8Request, _shellProcess, &Konsole::Pty::setUtf8Mode);
0163 
0164     // get notified when the pty process is finished
0165 #ifndef Q_OS_WIN
0166     connect(_shellProcess, &Konsole::Pty::finished, this, &Konsole::Session::done);
0167 #else
0168     connect(_shellProcess, &Konsole::Pty::finished, this, &Konsole::Session::done);
0169 #endif
0170 
0171     // emulator size
0172     connect(_emulation, &Konsole::Emulation::imageSizeChanged, this, &Konsole::Session::updateWindowSize);
0173     if (fd < 0 || runShell) {
0174         // Using a queued connection guarantees that starting the session
0175         // is delayed until all (both) image size updates at startup have
0176         // been processed. See #203185 and #412598.
0177         connect(_emulation, &Konsole::Emulation::imageSizeInitialized, this, &Konsole::Session::run, Qt::QueuedConnection);
0178     } else {
0179         // run needs to be disconnected, as it may be already connected by the constructor
0180         disconnect(_emulation, &Konsole::Emulation::imageSizeInitialized, this, &Konsole::Session::run);
0181     }
0182 }
0183 
0184 WId Session::windowId() const
0185 {
0186     // Returns a window ID for this session which is used
0187     // to set the WINDOWID environment variable in the shell
0188     // process.
0189     //
0190     // Sessions can have multiple views or no views, which means
0191     // that a single ID is not always going to be accurate.
0192     //
0193     // If there are no views, the window ID is just 0.  If
0194     // there are multiple views, then the window ID for the
0195     // top-level window which contains the first view is
0196     // returned
0197 
0198     if (_views.count() == 0) {
0199         return 0;
0200     } else {
0201         /**
0202          * compute the windows id to use
0203          * doesn't call winId on some widget, as this might lead
0204          * to rendering artifacts as this will trigger the
0205          * creation of a native window, see https://doc.qt.io/qt-5/qwidget.html#winId
0206          * instead, use https://doc.qt.io/qt-5/qwidget.html#effectiveWinId
0207          */
0208         QWidget *widget = _views.first();
0209         Q_ASSERT(widget);
0210         return widget->effectiveWinId();
0211     }
0212 }
0213 
0214 void Session::setDarkBackground(bool darkBackground)
0215 {
0216     _hasDarkBackground = darkBackground;
0217 }
0218 
0219 bool Session::isRunning() const
0220 {
0221 #ifdef Q_OS_WIN
0222     return (_shellProcess != nullptr) && _shellProcess->isRunning();
0223 #else
0224     return (_shellProcess != nullptr) && (_shellProcess->state() == QProcess::Running);
0225 #endif
0226 }
0227 
0228 bool Session::hasFocus() const
0229 {
0230     return std::any_of(_views.constBegin(), _views.constEnd(), [](const TerminalDisplay *display) {
0231         return display->hasFocus();
0232     });
0233 }
0234 
0235 void Session::setCodec(QTextCodec *codec)
0236 {
0237     if (isReadOnly()) {
0238         return;
0239     }
0240 
0241     emulation()->setCodec(codec);
0242 
0243     Q_EMIT sessionCodecChanged(codec);
0244 }
0245 
0246 bool Session::setCodec(const QByteArray &name)
0247 {
0248     QTextCodec *codec = QTextCodec::codecForName(name);
0249 
0250     if (codec != nullptr) {
0251         setCodec(codec);
0252         return true;
0253     } else {
0254         return false;
0255     }
0256 }
0257 
0258 QByteArray Session::codec()
0259 {
0260     return _emulation->codec()->name();
0261 }
0262 
0263 void Session::setProgram(const QString &program)
0264 {
0265     _program = ShellCommand::expand(program);
0266 }
0267 
0268 void Session::setArguments(const QStringList &arguments)
0269 {
0270     _arguments = ShellCommand::expand(arguments);
0271 }
0272 
0273 void Session::setInitialWorkingDirectory(const QString &dir)
0274 {
0275     _initialWorkingDir = validDirectory(KShell::tildeExpand(ShellCommand::expand(dir)));
0276 }
0277 
0278 QString Session::currentWorkingDirectory()
0279 {
0280     if (_reportedWorkingUrl.isValid() && _reportedWorkingUrl.isLocalFile()) {
0281         return _reportedWorkingUrl.path();
0282     }
0283 
0284     // only returned cached value
0285     if (_currentWorkingDir.isEmpty()) {
0286         updateWorkingDirectory();
0287     }
0288 
0289     return _currentWorkingDir;
0290 }
0291 void Session::updateWorkingDirectory()
0292 {
0293     updateSessionProcessInfo();
0294 
0295     const QString currentDir = _sessionProcessInfo->validCurrentDir();
0296     if (currentDir != _currentWorkingDir) {
0297         _currentWorkingDir = currentDir;
0298         Q_EMIT currentDirectoryChanged(_currentWorkingDir);
0299     }
0300 }
0301 
0302 QList<TerminalDisplay *> Session::views() const
0303 {
0304     return _views;
0305 }
0306 
0307 void Session::addView(TerminalDisplay *widget)
0308 {
0309     Q_ASSERT(!_views.contains(widget));
0310 
0311     _views.append(widget);
0312 
0313     // connect emulation - view signals and slots
0314     connect(widget, &Konsole::TerminalDisplay::keyPressedSignal, _emulation, &Konsole::Emulation::sendKeyEvent);
0315     connect(widget, &Konsole::TerminalDisplay::mouseSignal, _emulation, &Konsole::Emulation::sendMouseEvent);
0316     connect(widget, &Konsole::TerminalDisplay::sendStringToEmu, _emulation, &Konsole::Emulation::sendString);
0317     connect(widget, &Konsole::TerminalDisplay::peekPrimaryRequested, _emulation, &Konsole::Emulation::setPeekPrimary);
0318 
0319     // allow emulation to notify the view when the foreground process
0320     // indicates whether or not it is interested in Mouse Tracking events
0321     connect(_emulation, &Konsole::Emulation::programRequestsMouseTracking, widget, &Konsole::TerminalDisplay::setUsesMouseTracking);
0322 
0323     widget->setUsesMouseTracking(_emulation->programUsesMouseTracking());
0324 
0325     connect(_emulation, &Konsole::Emulation::enableAlternateScrolling, widget->scrollBar(), &Konsole::TerminalScrollBar::setAlternateScrolling);
0326 
0327     connect(_emulation, &Konsole::Emulation::programBracketedPasteModeChanged, widget, &Konsole::TerminalDisplay::setBracketedPasteMode);
0328 
0329     widget->setBracketedPasteMode(_emulation->programBracketedPasteMode());
0330 
0331     widget->setScreenWindow(_emulation->createWindow());
0332 
0333     _emulation->setCurrentTerminalDisplay(widget);
0334 
0335     // connect view signals and slots
0336     connect(widget, &Konsole::TerminalDisplay::changedContentSizeSignal, this, &Konsole::Session::onViewSizeChange);
0337 
0338     connect(widget, &Konsole::TerminalDisplay::destroyed, this, &Konsole::Session::viewDestroyed);
0339 
0340     connect(widget, &Konsole::TerminalDisplay::compositeFocusChanged, _emulation, &Konsole::Emulation::focusChanged);
0341 
0342     connect(_emulation, &Konsole::Emulation::setCursorStyleRequest, widget, &Konsole::TerminalDisplay::setCursorStyle);
0343     connect(_emulation, &Konsole::Emulation::resetCursorStyleRequest, widget, &Konsole::TerminalDisplay::resetCursorStyle);
0344 
0345     connect(widget, &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::Session::resetNotifications);
0346 }
0347 
0348 void Session::viewDestroyed(QObject *view)
0349 {
0350     auto *display = reinterpret_cast<TerminalDisplay *>(view);
0351 
0352     Q_ASSERT(_views.contains(display));
0353 
0354     removeView(display);
0355 }
0356 
0357 void Session::removeView(TerminalDisplay *widget)
0358 {
0359     _views.removeAll(widget);
0360 
0361     disconnect(widget, nullptr, this, nullptr);
0362 
0363     // disconnect
0364     //  - key presses signals from widget
0365     //  - mouse activity signals from widget
0366     //  - string sending signals from widget
0367     //
0368     //  ... and any other signals connected in addView()
0369     disconnect(widget, nullptr, _emulation, nullptr);
0370 
0371     // disconnect state change signals emitted by emulation
0372     disconnect(_emulation, nullptr, widget, nullptr);
0373 
0374     // close the session automatically when the last view is removed
0375     if (_views.count() == 0) {
0376         close();
0377     }
0378 }
0379 
0380 // Upon a KPty error, there is no description on what that error was...
0381 // Check to see if the given program is executable.
0382 QString Session::checkProgram(const QString &program)
0383 {
0384     QString exec = program;
0385 
0386     if (exec.isEmpty()) {
0387         return QString();
0388     }
0389 
0390 #ifndef Q_OS_WIN
0391     if (KSandbox::isFlatpak()) {
0392         QProcess proc;
0393         // run "test -x exec" on the host to see if the shell is executable
0394         proc.setProgram(QStringLiteral("test"));
0395         proc.setArguments(QStringList{QStringLiteral("-x"), exec});
0396         KSandbox::startHostProcess(proc, QProcess::ReadOnly);
0397         if (proc.waitForStarted() && proc.waitForFinished(-1)) {
0398             return proc.exitCode() == 0 ? exec : QString();
0399         }
0400         return {};
0401     }
0402 #endif // Q_OS_WIN
0403 
0404     QFileInfo info(exec);
0405     if (info.isAbsolute() && info.exists() && info.isExecutable()) {
0406         return exec;
0407     }
0408 
0409     exec = KIO::DesktopExecParser::executablePath(exec);
0410     exec = KShell::tildeExpand(exec);
0411     const QString pexec = QStandardPaths::findExecutable(exec);
0412     if (pexec.isEmpty()) {
0413         qCritical() << i18n("Could not find binary: ") << exec;
0414         return QString();
0415     }
0416 
0417     return exec;
0418 }
0419 
0420 void Session::terminalWarning(const QString &message)
0421 {
0422     static const QByteArray warningText = i18nc("@info:shell Alert the user with red color text", "Warning: ").toLocal8Bit();
0423     QByteArray messageText = message.toLocal8Bit();
0424 
0425     static const char redPenOn[] = "\033[1m\033[31m";
0426     static const char redPenOff[] = "\033[0m";
0427 
0428     _emulation->receiveData(redPenOn, qstrlen(redPenOn));
0429     _emulation->receiveData("\n\r\n\r", 4);
0430     _emulation->receiveData(warningText.constData(), qstrlen(warningText.constData()));
0431     _emulation->receiveData(messageText.constData(), qstrlen(messageText.constData()));
0432     _emulation->receiveData("\n\r\n\r", 4);
0433     _emulation->receiveData(redPenOff, qstrlen(redPenOff));
0434 }
0435 
0436 QString Session::shellSessionId() const
0437 {
0438     QString friendlyUuid(_uniqueIdentifier.toString());
0439     friendlyUuid.remove(QLatin1Char('-')).remove(QLatin1Char('{')).remove(QLatin1Char('}'));
0440 
0441     return friendlyUuid;
0442 }
0443 
0444 static QStringList postProcessArgs(const QStringList &args)
0445 {
0446 #ifndef Q_OS_WIN
0447     if (!KSandbox::isFlatpak()) {
0448         return args;
0449     }
0450     QStringList arguments;
0451     // last arg is the program
0452     arguments = args;
0453     QString program = arguments.back();
0454     arguments.pop_back();
0455     // Needs to be explicitly specified, KSandbox::makeHostContext
0456     // ignores any variables set in the system environment so this
0457     // never gets set.
0458     arguments.push_back(QStringLiteral("--env=TERM=xterm-256color"));
0459     arguments.push_back(program);
0460     return arguments;
0461 #else
0462     qCritical() << "Should never get called on windows";
0463     return {};
0464 #endif
0465 }
0466 
0467 void Session::run()
0468 {
0469     // FIXME: run() is called twice in some instances
0470     if (isRunning()) {
0471         qCDebug(KonsoleDebug) << "Attempted to re-run an already running session (" << processId() << ")";
0472         return;
0473     }
0474 
0475     // check that everything is in place to run the session
0476     if (_program.isEmpty()) {
0477         qWarning() << "Program to run not set.";
0478     }
0479     if (_arguments.isEmpty()) {
0480         qWarning() << "No command line arguments specified.";
0481     }
0482     if (_uniqueIdentifier.isNull()) {
0483         _uniqueIdentifier = QUuid::createUuid();
0484     }
0485 
0486     QStringList programs = {_program, QString::fromUtf8(qgetenv("SHELL")), QStringLiteral("/bin/sh")};
0487 
0488 #if HAVE_GETPWUID
0489     auto pw = getpwuid(getuid());
0490     // pw: Do not pass the returned pointer to free.
0491     if (pw != nullptr) {
0492         if (KSandbox::isFlatpak()) {
0493             QProcess proc;
0494             proc.setProgram(QStringLiteral("getent"));
0495             proc.setArguments({QStringLiteral("passwd"), QString::number(pw->pw_uid)});
0496             KSandbox::startHostProcess(proc);
0497             proc.waitForFinished();
0498             const auto shell = proc.readAllStandardOutput().simplified().split(':').at(6);
0499             programs.insert(1, QString::fromUtf8(shell));
0500         } else {
0501             programs.insert(1, QString::fromLocal8Bit(pw->pw_shell));
0502         }
0503     }
0504 #endif
0505 
0506     QString exec;
0507     for (const auto &choice : programs) {
0508         exec = checkProgram(choice);
0509         if (!exec.isEmpty()) {
0510             break;
0511         }
0512     }
0513 
0514     // if nothing could be found (not even the fallbacks), print a warning and do not run
0515     if (exec.isEmpty()) {
0516         terminalWarning(i18n("Could not find an interactive shell to start."));
0517         return;
0518     }
0519 
0520     // if a program was specified via setProgram(), but it couldn't be found (but a fallback was), print a warning
0521     if (exec != checkProgram(_program)) {
0522         terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.", _program, exec));
0523     } else if (exec != checkProgram(exec)) {
0524         terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.", exec, checkProgram(exec)));
0525     }
0526 
0527     // if no arguments are specified, fall back to program name
0528     QStringList arguments = _arguments.join(QLatin1Char(' ')).isEmpty() ? QStringList() << exec : _arguments;
0529 
0530     // For historical reasons, the first argument in _arguments is the
0531     // name of the program to execute, remove it in favor of the actual program name
0532     Q_ASSERT(arguments.count() >= 1);
0533     arguments = arguments.mid(1);
0534 
0535     if (!_initialWorkingDir.isEmpty()) {
0536         _shellProcess->setInitialWorkingDirectory(_initialWorkingDir);
0537     } else {
0538         _shellProcess->setInitialWorkingDirectory(QDir::currentPath());
0539     }
0540 
0541     _shellProcess->setFlowControlEnabled(_flowControlEnabled);
0542     _shellProcess->setEraseChar(_emulation->eraseChar());
0543 #ifndef Q_OS_WIN
0544     _shellProcess->setUseUtmp(_addToUtmp);
0545 #endif
0546 
0547 #ifndef Q_OS_WIN
0548     if (KSandbox::isFlatpak()) {
0549         _shellProcess->pty()->setCTtyEnabled(false); // not possibly inside sandbox
0550     }
0551 #endif
0552 
0553     // this is not strictly accurate use of the COLORFGBG variable.  This does not
0554     // tell the terminal exactly which colors are being used, but instead approximates
0555     // the color scheme as "black on white" or "white on black" depending on whether
0556     // the background color is deemed dark or not
0557     const QString backgroundColorHint = _hasDarkBackground ? QStringLiteral("COLORFGBG=15;0") : QStringLiteral("COLORFGBG=0;15");
0558     addEnvironmentEntry(backgroundColorHint);
0559 
0560     addEnvironmentEntry(QStringLiteral("SHELL_SESSION_ID=%1").arg(shellSessionId()));
0561 
0562     addEnvironmentEntry(QStringLiteral("WINDOWID=%1").arg(QString::number(windowId())));
0563 
0564     const QString dbusService = QDBusConnection::sessionBus().baseService();
0565     addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_SERVICE=%1").arg(dbusService));
0566 
0567     const QString dbusObject = QStringLiteral("/Sessions/%1").arg(QString::number(_sessionId));
0568     addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_SESSION=%1").arg(dbusObject));
0569 
0570 #ifndef Q_OS_WIN
0571     const auto originalEnvironment = _shellProcess->environment();
0572     _shellProcess->setProgram(exec);
0573     _shellProcess->setArguments(arguments);
0574     _shellProcess->setEnvironment(originalEnvironment + _environment);
0575     const auto context = KSandbox::makeHostContext(*_shellProcess);
0576     arguments = postProcessArgs(context.arguments);
0577     _shellProcess->setEnvironment(originalEnvironment);
0578     const auto result = _shellProcess->start(context.program, arguments, _environment);
0579 #else // Q_OS_WIN
0580     const auto size = _emulation->imageSize();
0581     const int lines = size.height();
0582     const int cols = size.width();
0583     int result = _shellProcess->start(exec, arguments, _initialWorkingDir.isEmpty() ? QDir::currentPath() : _initialWorkingDir, _environment, cols, lines);
0584 #endif
0585 
0586     if (result < 0) {
0587         terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec, arguments.join(QLatin1String(" "))));
0588         terminalWarning(_shellProcess->errorString());
0589         return;
0590     }
0591 
0592     _shellProcess->setWriteable(false); // We are reachable via kwrited.
0593 
0594     Q_EMIT started();
0595 }
0596 
0597 void Session::setSessionAttribute(int what, const QString &caption)
0598 {
0599     // set to true if anything has actually changed
0600     // eg. old _nameTitle != new _nameTitle
0601     bool modified = false;
0602 
0603     if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) {
0604         if (_userTitle != caption) {
0605             _userTitle = caption;
0606             modified = true;
0607         }
0608     }
0609 
0610     if ((what == IconNameAndWindowTitle) || (what == IconName)) {
0611         if (_iconText != caption) {
0612             _iconText = caption;
0613             modified = true;
0614         }
0615     }
0616 
0617     if (what == TextColor || what == BackgroundColor) {
0618         QString colorString = caption.section(QLatin1Char(';'), 0, 0);
0619         QColor color = QColor(colorString);
0620         if (color.isValid()) {
0621             if (what == TextColor) {
0622                 Q_EMIT changeForegroundColorRequest(color);
0623             } else {
0624                 Q_EMIT changeBackgroundColorRequest(color);
0625             }
0626         }
0627     }
0628 
0629     if (what == SessionName) {
0630         if (_localTabTitleFormat != caption) {
0631             _localTabTitleFormat = caption;
0632             setTitle(Session::DisplayedTitleRole, caption);
0633             modified = true;
0634         }
0635     }
0636 
0637     /* The below use of 32 works but appears to non-standard.
0638        It is from a commit from 2004 c20973eca8776f9b4f15bee5fdcb5a3205aa69de
0639      */
0640     // change icon via \033]32;Icon\007
0641     if (what == SessionIcon) {
0642         if (_iconName != caption) {
0643             _iconName = caption;
0644 
0645             modified = true;
0646         }
0647     }
0648 
0649     if (what == CurrentDirectory) {
0650         _reportedWorkingUrl = QUrl::fromUserInput(caption);
0651         Q_EMIT currentDirectoryChanged(currentWorkingDirectory());
0652         modified = true;
0653     }
0654 
0655     if (what == ProfileChange) {
0656         Q_EMIT profileChangeCommandReceived(caption);
0657         return;
0658     }
0659 
0660     if (modified) {
0661         Q_EMIT sessionAttributeChanged();
0662     }
0663 }
0664 
0665 QString Session::userTitle() const
0666 {
0667     return _userTitle;
0668 }
0669 
0670 void Session::setTabTitleFormat(TabTitleContext context, const QString &format)
0671 {
0672     if (context == LocalTabTitle) {
0673         _localTabTitleFormat = format;
0674         ProcessInfo *process = getProcessInfo();
0675         process->setUserNameRequired(format.contains(QLatin1String("%u")));
0676     } else if (context == RemoteTabTitle) {
0677         _remoteTabTitleFormat = format;
0678     }
0679 }
0680 
0681 QString Session::tabTitleFormat(TabTitleContext context) const
0682 {
0683     if (context == LocalTabTitle) {
0684         return _localTabTitleFormat;
0685     } else if (context == RemoteTabTitle) {
0686         return _remoteTabTitleFormat;
0687     }
0688 
0689     return QString();
0690 }
0691 
0692 void Session::tabTitleSetByUser(bool set)
0693 {
0694     _tabTitleSetByUser = set;
0695 }
0696 
0697 bool Session::isTabTitleSetByUser() const
0698 {
0699     return _tabTitleSetByUser;
0700 }
0701 
0702 void Session::tabColorSetByUser(bool set)
0703 {
0704     _tabColorSetByUser = set;
0705 }
0706 
0707 bool Session::isTabColorSetByUser() const
0708 {
0709     return _tabColorSetByUser;
0710 }
0711 
0712 void Session::silenceTimerDone()
0713 {
0714     // FIXME: The idea here is that the notification popup will appear to tell the user than output from
0715     // the terminal has stopped and the popup will disappear when the user activates the session.
0716     //
0717     // This breaks with the addition of multiple views of a session.  The popup should disappear
0718     // when any of the views of the session becomes active
0719 
0720     // FIXME: Make message text for this notification and the activity notification more descriptive.
0721     if (!_monitorSilence) {
0722         setPendingNotification(Notification::Silence, false);
0723         return;
0724     }
0725 
0726     TerminalDisplay *view = nullptr;
0727     if (!_views.isEmpty()) {
0728         view = _views.first();
0729     }
0730 
0731     KNotification *notification =
0732         new KNotification(hasFocus() ? QStringLiteral("Silence") : QStringLiteral("SilenceHidden"), KNotification::CloseWhenWindowActivated);
0733     notification->setWindow(view->windowHandle());
0734 
0735     notification->setText(i18n("Silence in '%1' (Session '%2')", _displayTitle, _nameTitle));
0736     auto action = notification->addDefaultAction(i18n("Show session"));
0737     connect(action, &KNotificationAction::activated, this, [view, notification]() {
0738         view->notificationClicked(notification->xdgActivationToken());
0739     });
0740     if (view->sessionController()->isMonitorOnce()) {
0741         view->sessionController()->actionCollection()->action(QStringLiteral("monitor-silence"))->setChecked(false);
0742     }
0743     setPendingNotification(Notification::Silence);
0744 }
0745 
0746 void Session::activityTimerDone()
0747 {
0748     _notifiedActivity = false;
0749 }
0750 
0751 void Session::resetNotifications()
0752 {
0753     static const Notification availableNotifications[] = {Activity, Silence, Bell};
0754     for (auto notification : availableNotifications) {
0755         setPendingNotification(notification, false);
0756     }
0757 }
0758 
0759 void Session::updateFlowControlState(bool suspended)
0760 {
0761     if (suspended) {
0762         if (flowControlEnabled()) {
0763             for (TerminalDisplay *display : std::as_const(_views)) {
0764                 if (display->flowControlWarningEnabled()) {
0765                     display->outputSuspended(true);
0766                 }
0767             }
0768         }
0769     } else {
0770         for (TerminalDisplay *display : std::as_const(_views)) {
0771             display->outputSuspended(false);
0772         }
0773     }
0774 }
0775 
0776 void Session::onPrimaryScreenInUse(bool use)
0777 {
0778     _isPrimaryScreen = use;
0779     Q_EMIT primaryScreenInUse(use);
0780 }
0781 
0782 bool Session::isPrimaryScreen()
0783 {
0784     return _isPrimaryScreen;
0785 }
0786 
0787 void Session::sessionAttributeRequest(int id, uint terminator)
0788 {
0789     switch (id) {
0790     case TextColor:
0791         // Get 'TerminalDisplay' (_view) foreground color
0792         Q_EMIT getForegroundColor(terminator);
0793         break;
0794     case BackgroundColor:
0795         // Get 'TerminalDisplay' (_view) background color
0796         Q_EMIT getBackgroundColor(terminator);
0797         break;
0798     }
0799 }
0800 
0801 void Session::onViewSizeChange(int /* height */, int /* width */)
0802 {
0803     updateTerminalSize();
0804 }
0805 
0806 void Session::updateTerminalSize()
0807 {
0808     int minLines = -1;
0809     int minColumns = -1;
0810 
0811     // minimum number of lines and columns that views require for
0812     // their size to be taken into consideration ( to avoid problems
0813     // with new view widgets which haven't yet been set to their correct size )
0814     const int VIEW_LINES_THRESHOLD = 2;
0815     const int VIEW_COLUMNS_THRESHOLD = 2;
0816 
0817     // select largest number of lines and columns that will fit in all visible views
0818     for (TerminalDisplay *view : std::as_const(_views)) {
0819         if (!view->isHidden() && view->lines() >= VIEW_LINES_THRESHOLD && view->columns() >= VIEW_COLUMNS_THRESHOLD) {
0820             minLines = (minLines == -1) ? view->lines() : qMin(minLines, view->lines());
0821             minColumns = (minColumns == -1) ? view->columns() : qMin(minColumns, view->columns());
0822             view->processFilters();
0823         }
0824     }
0825 
0826     // backend emulation must have a _terminal of at least 1 column x 1 line in size
0827     if (minLines > 0 && minColumns > 0) {
0828         _emulation->setImageSize(minLines, minColumns);
0829     }
0830 }
0831 void Session::updateWindowSize(int lines, int columns)
0832 {
0833     Q_ASSERT(lines > 0 && columns > 0);
0834 
0835     int width = 0;
0836     int height = 0;
0837     if (!_views.isEmpty()) {
0838         // This is somewhat arbitrary. Views having potentially different font sizes is
0839         // irreconcilable with the PTY user having accurate knowledge of the geometry.
0840         QSize cr = _views.at(0)->contentRect().size();
0841         width = cr.width();
0842         height = cr.height();
0843     }
0844     _shellProcess->setWindowSize(columns, lines, width, height);
0845 }
0846 void Session::refresh()
0847 {
0848     // attempt to get the shell process to redraw the display
0849     //
0850     // this requires the program running in the shell
0851     // to cooperate by sending an update in response to
0852     // a window size change
0853     //
0854     // the window size is changed twice, first made slightly larger and then
0855     // resized back to its normal size so that there is actually a change
0856     // in the window size (some shells do nothing if the
0857     // new and old sizes are the same)
0858     //
0859     // if there is a more 'correct' way to do this, please
0860     // send an email with method or patches to konsole-devel@kde.org
0861 
0862     const QSize existingSize = _shellProcess->windowSize();
0863     const QSize existingPxSize = _shellProcess->pixelSize();
0864     _shellProcess->setWindowSize(existingSize.width() + 1, existingSize.height(), existingPxSize.width() + 1, existingPxSize.height());
0865     // introduce small delay to avoid changing size too quickly
0866     QThread::usleep(500);
0867     _shellProcess->setWindowSize(existingSize.width(), existingSize.height(), existingPxSize.width(), existingPxSize.height());
0868 }
0869 
0870 void Session::sendSignal(int signal)
0871 {
0872 #ifndef Q_OS_WIN
0873     const ProcessInfo *process = getProcessInfo();
0874     bool ok = false;
0875     int pid;
0876     pid = process->foregroundPid(&ok);
0877 
0878     if (ok) {
0879         ::kill(pid, signal);
0880     } else {
0881         qWarning() << "foreground process id not set, unable to send signal " << signal;
0882     }
0883 #else
0884     // FIXME: Can we do this on windows?
0885 #endif
0886 }
0887 
0888 void Session::reportColor(SessionAttributes r, const QColor &c, uint terminator)
0889 {
0890 #define to65k(a) (QStringLiteral("%1").arg(int(((a)*0xFFFF)), 4, 16, QLatin1Char('0')))
0891     QString msg = QStringLiteral("\033]%1;rgb:").arg(r) + to65k(c.redF()) + QLatin1Char('/') + to65k(c.greenF()) + QLatin1Char('/') + to65k(c.blueF());
0892 
0893     // Match termination of OSC reply to termination of OSC request.
0894     if (terminator == '\a') { // non standard BEL terminator
0895         msg += QLatin1Char('\a');
0896     } else { // standard 7-bit ST terminator
0897         msg += QStringLiteral("\033\\");
0898     }
0899     _emulation->sendString(msg.toUtf8());
0900 #undef to65k
0901 }
0902 
0903 void Session::reportForegroundColor(const QColor &c, uint terminator)
0904 {
0905     reportColor(SessionAttributes::TextColor, c, terminator);
0906 }
0907 
0908 void Session::reportBackgroundColor(const QColor &c, uint terminator)
0909 {
0910     reportColor(SessionAttributes::BackgroundColor, c, terminator);
0911 }
0912 
0913 bool Session::kill(int signal)
0914 {
0915 #ifndef Q_OS_WIN
0916     if (processId() <= 0) {
0917         return false;
0918     }
0919 
0920     int result = ::kill(processId(), signal);
0921 
0922     if (result == 0) {
0923         return _shellProcess->waitForFinished(1000);
0924     } else {
0925         return false;
0926     }
0927 #else
0928     return false;
0929 #endif
0930 }
0931 
0932 void Session::close()
0933 {
0934     if (isRunning()) {
0935         if (!closeInNormalWay()) {
0936             closeInForceWay();
0937         }
0938     } else {
0939         // terminal process has finished, just close the session
0940         QTimer::singleShot(1, this, [this]() {
0941             Q_EMIT finished(this);
0942         });
0943     }
0944 }
0945 
0946 bool Session::closeInNormalWay()
0947 {
0948 #ifdef Q_OS_WIN
0949     _shellProcess->closePty();
0950     return true;
0951 #else
0952     _autoClose = true;
0953     _closePerUserRequest = true;
0954 
0955     // for the possible case where following events happen in sequence:
0956     //
0957     // 1). the terminal process crashes
0958     // 2). the tab stays open and displays warning message
0959     // 3). the user closes the tab explicitly
0960     //
0961     if (!isRunning()) {
0962         Q_EMIT finished(this);
0963         return true;
0964     }
0965 
0966     // try SIGHUP, afterwards do hard kill
0967     // this is the sequence used by most other terminal emulators like xterm, gnome-terminal, ...
0968     // see bug 401898 for details about tries to have some "soft-terminate" via EOF character
0969     if (kill(SIGHUP)) {
0970         return true;
0971     }
0972 
0973     qWarning() << "Process " << processId() << " did not die with SIGHUP";
0974     _shellProcess->closePty();
0975     return (_shellProcess->waitForFinished(1000));
0976 #endif
0977 }
0978 
0979 bool Session::closeInForceWay()
0980 {
0981     _autoClose = true;
0982     _closePerUserRequest = true;
0983 
0984 #ifdef Q_OS_WIN
0985     return _shellProcess->kill();
0986 #else
0987     if (kill(SIGKILL)) {
0988         return true;
0989     } else {
0990         qWarning() << "Process " << processId() << " did not die with SIGKILL";
0991         return false;
0992     }
0993 #endif
0994 }
0995 
0996 void Session::sendTextToTerminal(const QString &text, const QChar &eol) const
0997 {
0998     if (isReadOnly()) {
0999         return;
1000     }
1001 
1002     if (eol.isNull()) {
1003         _emulation->sendText(text);
1004     } else {
1005         _emulation->sendText(text + eol);
1006     }
1007 }
1008 
1009 // Only D-Bus calls this function (via SendText or runCommand)
1010 void Session::sendText(const QString &text) const
1011 {
1012     if (isReadOnly()) {
1013         return;
1014     }
1015 
1016 #if !REMOVE_SENDTEXT_RUNCOMMAND_DBUS_METHODS
1017     if (show_disallow_certain_dbus_methods_message) {
1018         KNotification::event(KNotification::Warning,
1019                              QStringLiteral("Konsole D-Bus Warning"),
1020                              i18n("The D-Bus methods sendText/runCommand were just used.  There are security concerns about allowing these methods to be "
1021                                   "public.  If desired, these methods can be changed to internal use only by re-compiling Konsole. <p>This warning will only "
1022                                   "show once for this Konsole instance.</p>"));
1023 
1024         show_disallow_certain_dbus_methods_message = false;
1025     }
1026 #endif
1027 
1028     _emulation->sendText(text);
1029 }
1030 
1031 // Only D-Bus calls this function
1032 void Session::runCommand(const QString &command) const
1033 {
1034     if (isReadOnly()) {
1035         return;
1036     }
1037 
1038     sendText(command + QLatin1Char('\n'));
1039 }
1040 
1041 void Session::sendMouseEvent(int buttons, int column, int line, int eventType)
1042 {
1043     if (isReadOnly()) {
1044         return;
1045     }
1046 
1047     _emulation->sendMouseEvent(buttons, column, line, eventType);
1048 }
1049 
1050 void Session::done(int exitCode, QProcess::ExitStatus exitStatus)
1051 {
1052 #ifndef Q_OS_WIN
1053     // This slot should be triggered only one time
1054     disconnect(_shellProcess, &Konsole::Pty::finished, this, &Konsole::Session::done);
1055 #else
1056     disconnect(_shellProcess, &Konsole::Pty::finished, this, &Konsole::Session::done);
1057 #endif
1058 
1059     if (!_autoClose) {
1060         _userTitle = i18nc("@info:shell This session is done", "Finished");
1061         Q_EMIT sessionAttributeChanged();
1062         return;
1063     }
1064 
1065     if (_closePerUserRequest) {
1066         Q_EMIT finished(this);
1067         return;
1068     }
1069 
1070     QString message;
1071 
1072     if (exitCode != 0) {
1073         if (exitStatus != QProcess::NormalExit) {
1074             message = i18n("Program '%1' crashed.", _program);
1075         } else {
1076             message = i18n("Program '%1' exited with status %2.", _program, exitCode);
1077         }
1078 
1079         // FIXME: See comments in Session::silenceTimerDone()
1080         KNotification *notification = new KNotification(QStringLiteral("Finished"), KNotification::CloseWhenWindowActivated);
1081         if (QApplication::activeWindow()) {
1082             notification->setWindow(QApplication::activeWindow()->windowHandle());
1083         }
1084         notification->setText(message);
1085         notification->sendEvent();
1086     }
1087 
1088     if (exitStatus != QProcess::NormalExit) {
1089         // this seeming duplicated line is for the case when exitCode is 0
1090         message = i18n("Program '%1' crashed.", _program);
1091         terminalWarning(message);
1092     } else {
1093         Q_EMIT finished(this);
1094     }
1095 }
1096 
1097 Emulation *Session::emulation() const
1098 {
1099     return _emulation;
1100 }
1101 
1102 QString Session::keyBindings() const
1103 {
1104     return _emulation->keyBindings();
1105 }
1106 
1107 QStringList Session::environment() const
1108 {
1109     return _environment;
1110 }
1111 
1112 void Session::setEnvironment(const QStringList &environment)
1113 {
1114     if (isReadOnly()) {
1115         return;
1116     }
1117 
1118     _environment = environment;
1119 }
1120 
1121 void Session::addEnvironmentEntry(const QString &entry)
1122 {
1123     _environment << entry;
1124 }
1125 
1126 int Session::sessionId() const
1127 {
1128     return _sessionId;
1129 }
1130 
1131 void Session::setKeyBindings(const QString &name)
1132 {
1133     _emulation->setKeyBindings(name);
1134 }
1135 
1136 void Session::setTitle(TitleRole role, const QString &newTitle)
1137 {
1138     if (title(role) != newTitle) {
1139         if (role == NameRole) {
1140             _nameTitle = newTitle;
1141         } else if (role == DisplayedTitleRole) {
1142             _displayTitle = newTitle;
1143         }
1144 
1145         Q_EMIT sessionAttributeChanged();
1146     }
1147 }
1148 
1149 QString Session::title(TitleRole role) const
1150 {
1151     if (role == NameRole) {
1152         return _nameTitle;
1153     } else if (role == DisplayedTitleRole) {
1154         return _displayTitle;
1155     } else {
1156         return QString();
1157     }
1158 }
1159 
1160 ProcessInfo *Session::getProcessInfo()
1161 {
1162     ProcessInfo *process = nullptr;
1163 
1164     if (isForegroundProcessActive() && updateForegroundProcessInfo()) {
1165         process = _foregroundProcessInfo;
1166     } else {
1167         updateSessionProcessInfo();
1168         process = _sessionProcessInfo;
1169     }
1170 
1171     return process;
1172 }
1173 
1174 void Session::updateSessionProcessInfo()
1175 {
1176     Q_ASSERT(_shellProcess);
1177 
1178     bool ok;
1179     // The checking for pid changing looks stupid, but it is needed
1180     // at the moment to workaround the problem that processId() might
1181     // return 0
1182     if ((_sessionProcessInfo == nullptr) || (processId() != 0 && processId() != _sessionProcessInfo->pid(&ok))) {
1183         delete _sessionProcessInfo;
1184         _sessionProcessInfo = ProcessInfo::newInstance(processId());
1185         _sessionProcessInfo->setUserHomeDir();
1186     }
1187     _sessionProcessInfo->update();
1188 }
1189 
1190 bool Session::updateForegroundProcessInfo()
1191 {
1192     Q_ASSERT(_shellProcess);
1193 
1194     const int foregroundPid = _shellProcess->foregroundProcessGroup();
1195     if (foregroundPid != _foregroundPid) {
1196         delete _foregroundProcessInfo;
1197         _foregroundProcessInfo = ProcessInfo::newInstance(foregroundPid, processId());
1198         _foregroundPid = foregroundPid;
1199     }
1200 
1201     if (_foregroundProcessInfo != nullptr) {
1202         _foregroundProcessInfo->update();
1203         return _foregroundProcessInfo->isValid();
1204     } else {
1205         return false;
1206     }
1207 }
1208 
1209 bool Session::isRemote()
1210 {
1211     ProcessInfo *process = getProcessInfo();
1212 
1213     bool ok = false;
1214     return (process->name(&ok) == QLatin1String("ssh") && ok);
1215 }
1216 
1217 QString Session::getDynamicTitle()
1218 {
1219     ProcessInfo *process = getProcessInfo();
1220     std::unique_ptr<SSHProcessInfo> sshProcess;
1221 
1222     // format tab titles using process info
1223     bool ok = false;
1224     if (process->name(&ok) == QLatin1String("ssh") && ok) {
1225         process->refreshArguments();
1226         sshProcess = std::make_unique<SSHProcessInfo>(*process);
1227     }
1228 
1229     QString currHostName = sshProcess ? sshProcess->host() : process->localHost();
1230 
1231     if (_currentHostName != currHostName) {
1232         _currentHostName = currHostName;
1233         Q_EMIT hostnameChanged(currHostName);
1234     }
1235 
1236     if (sshProcess) {
1237         QString title = tabTitleFormat(Session::RemoteTabTitle);
1238         title.replace(QLatin1String("%w"), userTitle());
1239         title.replace(QLatin1String("%#"), QString::number(sessionId()));
1240         return sshProcess->format(title);
1241     }
1242 
1243     /*
1244      * Parses an input string, looking for markers beginning with a '%'
1245      * character and returns a string with the markers replaced
1246      * with information from this process description.
1247      * <br>
1248      * The markers recognized are:
1249      * <ul>
1250      * <li> %B - User's Bourne prompt sigil ($, or # for superuser). </li>
1251      * <li> %u - Name of the user which owns the process. </li>
1252      * <li> %n - Replaced with the name of the process.   </li>
1253      * <li> %d - Replaced with the last part of the path name of the
1254      *      process' current working directory.
1255      *
1256      *      (eg. if the current directory is '/home/bob' then
1257      *      'bob' would be returned)
1258      * </li>
1259      * <li> %D - Replaced with the current working directory of the process. </li>
1260      * <li> %h - Replaced with the local host name. <li>
1261      * <li> %w - Replaced with the window title set by the shell. </li>
1262      * <li> %# - Replaced with the number of the session. <li>
1263      * </ul>
1264      */
1265     QString title = tabTitleFormat(Session::LocalTabTitle);
1266     // search for and replace known marker
1267 
1268     QString dir = _reportedWorkingUrl.toLocalFile();
1269     bool dirOk = true;
1270     if (dir.isEmpty()) {
1271         // update current directory from process
1272         updateWorkingDirectory();
1273         // Previous process may have been freed in updateSessionProcessInfo()
1274         process = getProcessInfo();
1275         dir = process->currentDir(&dirOk);
1276     }
1277 
1278     int pos = 0;
1279     while ((pos = title.indexOf(QLatin1Char('%'), pos)) != -1) {
1280         if (pos >= title.size() - 1) {
1281             break;
1282         }
1283 
1284         switch (title.at(pos + 1).toLatin1()) {
1285         case 'B': {
1286             int UID = process->userId(&ok);
1287             if (!ok) {
1288                 title.replace(pos, 2, QStringLiteral("-"));
1289                 pos++;
1290             } else {
1291                 // title.replace(QLatin1String("%I"), QString::number(UID));
1292                 if (UID == 0) {
1293                     title.replace(pos, 2, QStringLiteral("#"));
1294                     pos++;
1295                 } else {
1296                     title.replace(pos, 2, QStringLiteral("$"));
1297                     pos++;
1298                 }
1299             }
1300         } break;
1301         case 'u': {
1302             QString replacement = process->userName();
1303             title.replace(pos, 2, replacement);
1304             pos += replacement.size();
1305         } break;
1306         case 'h': {
1307             QString replacement = Konsole::ProcessInfo::localHost();
1308             title.replace(pos, 2, replacement);
1309             pos += replacement.size();
1310         } break;
1311         case 'n': {
1312             QString replacement = process->name(&ok);
1313             title.replace(pos, 2, replacement);
1314             pos += replacement.size();
1315         } break;
1316         case 'w': {
1317             QString replacement = userTitle();
1318             title.replace(pos, 2, replacement);
1319             pos += replacement.size();
1320         } break;
1321         case '#': {
1322             QString replacement = QString::number(sessionId());
1323             title.replace(pos, 2, replacement);
1324             pos += replacement.size();
1325         } break;
1326         case 'd':
1327             if (!dirOk) {
1328                 title.replace(pos, 2, QStringLiteral("-"));
1329                 pos++;
1330             } else {
1331                 // allow for shortname to have the ~ as homeDir
1332                 const QString homeDir = process->userHomeDir();
1333                 if (!homeDir.isEmpty()) {
1334                     if (dir.startsWith(homeDir)) {
1335                         dir.remove(0, homeDir.length());
1336                         dir.prepend(QLatin1Char('~'));
1337                     }
1338                 }
1339                 const QString replacement = process->formatShortDir(dir);
1340                 title.replace(pos, 2, replacement);
1341                 pos += replacement.size();
1342             }
1343             break;
1344         case 'D':
1345             if (!dirOk) {
1346                 title.replace(pos, 2, QStringLiteral("-"));
1347                 pos++;
1348             } else {
1349                 // allow for shortname to have the ~ as homeDir
1350                 const QString homeDir = process->userHomeDir();
1351                 if (!homeDir.isEmpty()) {
1352                     if (dir.startsWith(homeDir)) {
1353                         dir.remove(0, homeDir.length());
1354                         dir.prepend(QLatin1Char('~'));
1355                     }
1356                 }
1357                 title.replace(pos, 2, dir);
1358                 pos += dir.size();
1359             }
1360             break;
1361         default:
1362             pos++;
1363         }
1364     }
1365 
1366     return title;
1367 }
1368 
1369 QUrl Session::getUrl()
1370 {
1371     if (_reportedWorkingUrl.isValid()) {
1372         return _reportedWorkingUrl;
1373     }
1374 
1375     QString path;
1376 
1377     updateSessionProcessInfo();
1378     if (_sessionProcessInfo->isValid()) {
1379         bool ok = false;
1380 
1381         // check if foreground process is bookmark-able
1382         if (isForegroundProcessActive() && updateForegroundProcessInfo() && _foregroundProcessInfo->isValid()) {
1383             // for remote connections, save the user and host
1384             // bright ideas to get the directory at the other end are welcome :)
1385             if (_foregroundProcessInfo->name(&ok) == QLatin1String("ssh") && ok) {
1386                 SSHProcessInfo sshInfo(*_foregroundProcessInfo);
1387 
1388                 QUrl url;
1389                 url.setScheme(QStringLiteral("ssh"));
1390                 url.setUserName(sshInfo.userName());
1391                 url.setHost(sshInfo.host());
1392 
1393                 const QString port = sshInfo.port();
1394                 if (!port.isEmpty() && port != QLatin1String("22")) {
1395                     url.setPort(port.toInt());
1396                 }
1397                 return url;
1398             } else {
1399                 path = _foregroundProcessInfo->currentDir(&ok);
1400                 if (!ok) {
1401                     path.clear();
1402                 }
1403             }
1404         } else { // otherwise use the current working directory of the shell process
1405             path = _sessionProcessInfo->currentDir(&ok);
1406             if (!ok) {
1407                 path.clear();
1408             }
1409         }
1410     }
1411 
1412     return QUrl::fromLocalFile(path);
1413 }
1414 
1415 void Session::setIconName(const QString &iconName)
1416 {
1417     if (iconName != _iconName) {
1418         _iconName = iconName;
1419         Q_EMIT sessionAttributeChanged();
1420     }
1421 }
1422 
1423 void Session::setIconText(const QString &iconText)
1424 {
1425     _iconText = iconText;
1426 }
1427 
1428 QString Session::iconName() const
1429 {
1430     return _iconName;
1431 }
1432 
1433 QString Session::iconText() const
1434 {
1435     return _iconText;
1436 }
1437 
1438 void Session::setHistoryType(const HistoryType &hType)
1439 {
1440     _emulation->setHistory(hType);
1441 }
1442 
1443 const HistoryType &Session::historyType() const
1444 {
1445     return _emulation->history();
1446 }
1447 
1448 void Session::clearHistory()
1449 {
1450     _emulation->clearHistory();
1451 }
1452 
1453 QStringList Session::arguments() const
1454 {
1455     return _arguments;
1456 }
1457 
1458 QString Session::program() const
1459 {
1460     return _program;
1461 }
1462 
1463 bool Session::isMonitorPrompt() const
1464 {
1465     return _monitorPrompt;
1466 }
1467 bool Session::isMonitorActivity() const
1468 {
1469     return _monitorActivity;
1470 }
1471 bool Session::isMonitorSilence() const
1472 {
1473     return _monitorSilence;
1474 }
1475 
1476 void Session::setMonitorPrompt(bool monitor)
1477 {
1478     if (_monitorPrompt == monitor) {
1479         return;
1480     }
1481     _monitorPrompt = monitor;
1482 }
1483 
1484 void Session::setMonitorActivity(bool monitor)
1485 {
1486     if (_monitorActivity == monitor) {
1487         return;
1488     }
1489 
1490     _monitorActivity = monitor;
1491     _notifiedActivity = false;
1492 
1493     // This timer is meaningful only after activity has been notified
1494     _activityTimer->stop();
1495 
1496     setPendingNotification(Notification::Activity, false);
1497 }
1498 
1499 void Session::setMonitorSilence(bool monitor)
1500 {
1501     if (_monitorSilence == monitor) {
1502         return;
1503     }
1504 
1505     _monitorSilence = monitor;
1506     if (_monitorSilence) {
1507         _silenceTimer->start(_silenceSeconds * 1000);
1508     } else {
1509         _silenceTimer->stop();
1510     }
1511 
1512     setPendingNotification(Notification::Silence, false);
1513 }
1514 
1515 void Session::setMonitorSilenceSeconds(int seconds)
1516 {
1517     _silenceSeconds = seconds;
1518     if (_monitorSilence) {
1519         _silenceTimer->start(_silenceSeconds * 1000);
1520     }
1521 }
1522 
1523 void Session::setAddToUtmp(bool add)
1524 {
1525     _addToUtmp = add;
1526 }
1527 
1528 void Session::setAutoClose(bool close)
1529 {
1530     _autoClose = close;
1531 }
1532 
1533 bool Session::autoClose() const
1534 {
1535     return _autoClose;
1536 }
1537 
1538 void Session::setFlowControlEnabled(bool enabled)
1539 {
1540     if (isReadOnly()) {
1541         return;
1542     }
1543 
1544     _flowControlEnabled = enabled;
1545 
1546     if (_shellProcess != nullptr) {
1547         _shellProcess->setFlowControlEnabled(_flowControlEnabled);
1548     }
1549 
1550     Q_EMIT flowControlEnabledChanged(enabled);
1551 }
1552 bool Session::flowControlEnabled() const
1553 {
1554     if (_shellProcess != nullptr) {
1555         return _shellProcess->flowControlEnabled();
1556     } else {
1557         return _flowControlEnabled;
1558     }
1559 }
1560 void Session::fireZModemDownloadDetected()
1561 {
1562     if (!_zmodemBusy) {
1563         QTimer::singleShot(10, this, &Konsole::Session::zmodemDownloadDetected);
1564         _zmodemBusy = true;
1565     }
1566 }
1567 
1568 void Session::fireZModemUploadDetected()
1569 {
1570     if (!_zmodemBusy) {
1571         QTimer::singleShot(10, this, &Konsole::Session::zmodemUploadDetected);
1572     }
1573 }
1574 
1575 void Session::cancelZModem()
1576 {
1577     _shellProcess->sendData(QByteArrayLiteral("\030\030\030\030")); // Abort
1578     _zmodemBusy = false;
1579 }
1580 
1581 void Session::startZModem(const QString &zmodem, const QString &dir, const QStringList &list)
1582 {
1583     _zmodemBusy = true;
1584     _zmodemProc = new KProcess();
1585     _zmodemProc->setOutputChannelMode(KProcess::SeparateChannels);
1586 
1587     *_zmodemProc << zmodem << QStringLiteral("-v") << QStringLiteral("-e") << list;
1588 
1589     if (!dir.isEmpty()) {
1590         _zmodemProc->setWorkingDirectory(dir);
1591     }
1592 
1593     connect(_zmodemProc, &KProcess::readyReadStandardOutput, this, &Konsole::Session::zmodemReadAndSendBlock);
1594     connect(_zmodemProc, &KProcess::readyReadStandardError, this, &Konsole::Session::zmodemReadStatus);
1595     connect(_zmodemProc, &KProcess::finished, this, &Konsole::Session::zmodemFinished);
1596 
1597     _zmodemProc->start();
1598 
1599     disconnect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock);
1600     connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::zmodemReceiveBlock);
1601 
1602     _zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false, i18n("ZModem Progress"));
1603 
1604     connect(_zmodemProgress, &Konsole::ZModemDialog::zmodemCancel, this, &Konsole::Session::zmodemFinished);
1605 
1606     _zmodemProgress->show();
1607 }
1608 
1609 void Session::zmodemReadAndSendBlock()
1610 {
1611     _zmodemProc->setReadChannel(QProcess::StandardOutput);
1612     QByteArray data = _zmodemProc->read(ZMODEM_BUFFER_SIZE);
1613 
1614     while (!data.isEmpty()) {
1615         _shellProcess->sendData(data);
1616         data = _zmodemProc->read(ZMODEM_BUFFER_SIZE);
1617     }
1618 }
1619 
1620 void Session::zmodemReadStatus()
1621 {
1622     _zmodemProc->setReadChannel(QProcess::StandardError);
1623     QByteArray msg = _zmodemProc->readAll();
1624     while (!msg.isEmpty()) {
1625         int i = msg.indexOf('\015');
1626         int j = msg.indexOf('\012');
1627         QByteArray txt;
1628         if ((i != -1) && ((j == -1) || (i < j))) {
1629             msg = msg.mid(i + 1);
1630         } else if (j != -1) {
1631             txt = msg.left(j);
1632             msg = msg.mid(j + 1);
1633         } else {
1634             txt = msg;
1635             msg.truncate(0);
1636         }
1637         if (!txt.isEmpty()) {
1638             _zmodemProgress->addText(QString::fromLocal8Bit(txt));
1639         }
1640     }
1641 }
1642 
1643 void Session::zmodemReceiveBlock(const char *data, int len)
1644 {
1645     static int steps = 0;
1646     QByteArray bytes(data, len);
1647 
1648     _zmodemProc->write(bytes);
1649 
1650     // Provide some feedback to dialog
1651     if (steps > 100) {
1652         _zmodemProgress->addProgressText(QStringLiteral("."));
1653         steps = 0;
1654     }
1655     steps++;
1656 }
1657 
1658 void Session::zmodemFinished()
1659 {
1660     /* zmodemFinished() is called by QProcess's finished() and
1661        ZModemDialog's user1Clicked(). Therefore, an invocation by
1662        user1Clicked() will recursively invoke this function again
1663        when the KProcess is deleted! */
1664     if (_zmodemProc != nullptr) {
1665         KProcess *process = _zmodemProc;
1666         _zmodemProc = nullptr; // Set _zmodemProc to 0 avoid recursive invocations!
1667         _zmodemBusy = false;
1668         delete process; // Now, the KProcess may be disposed safely.
1669 
1670         disconnect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::zmodemReceiveBlock);
1671         connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock);
1672 
1673         _shellProcess->sendData(QByteArrayLiteral("\030\030\030\030")); // Abort
1674         _shellProcess->sendData(QByteArrayLiteral("\001\013\n")); // Try to get prompt back
1675         _zmodemProgress->transferDone();
1676     }
1677 }
1678 
1679 void Session::onReceiveBlock(const char *buf, int len)
1680 {
1681     handleActivity();
1682     _emulation->receiveData(buf, len);
1683 }
1684 
1685 QSize Session::size()
1686 {
1687     return _emulation->imageSize();
1688 }
1689 
1690 void Session::setSize(const QSize &size)
1691 {
1692     if ((size.width() <= 1) || (size.height() <= 1)) {
1693         return;
1694     }
1695 
1696     Q_EMIT resizeRequest(size);
1697 }
1698 
1699 QSize Session::preferredSize() const
1700 {
1701     return _preferredSize;
1702 }
1703 
1704 void Session::setPreferredSize(const QSize &size)
1705 {
1706     _preferredSize = size;
1707 }
1708 
1709 int Session::processId() const
1710 {
1711     return _shellProcess->processId();
1712 }
1713 
1714 void Session::setTitle(int role, const QString &title)
1715 {
1716     switch (role) {
1717     case 0:
1718         setTitle(Session::NameRole, title);
1719         break;
1720     case 1:
1721         setTitle(Session::DisplayedTitleRole, title);
1722 
1723         // without these, that title will be overridden by the expansion of
1724         // title format shortly after, which will confuses users.
1725         _localTabTitleFormat = title;
1726         _remoteTabTitleFormat = title;
1727 
1728         break;
1729     }
1730 }
1731 
1732 QString Session::title(int role) const
1733 {
1734     switch (role) {
1735     case 0:
1736         return title(Session::NameRole);
1737     case 1:
1738         return title(Session::DisplayedTitleRole);
1739     default:
1740         return QString();
1741     }
1742 }
1743 
1744 void Session::setTabTitleFormat(int context, const QString &format)
1745 {
1746     switch (context) {
1747     case 0:
1748         setTabTitleFormat(Session::LocalTabTitle, format);
1749         break;
1750     case 1:
1751         setTabTitleFormat(Session::RemoteTabTitle, format);
1752         break;
1753     }
1754 }
1755 
1756 QString Session::tabTitleFormat(int context) const
1757 {
1758     switch (context) {
1759     case 0:
1760         return tabTitleFormat(Session::LocalTabTitle);
1761     case 1:
1762         return tabTitleFormat(Session::RemoteTabTitle);
1763     default:
1764         return QString();
1765     }
1766 }
1767 
1768 void Session::setHistorySize(int lines)
1769 {
1770     if (isReadOnly()) {
1771         return;
1772     }
1773 
1774     if (lines < 0) {
1775         setHistoryType(HistoryTypeFile());
1776     } else if (lines == 0) {
1777         setHistoryType(HistoryTypeNone());
1778     } else {
1779         setHistoryType(CompactHistoryType(lines));
1780     }
1781 }
1782 
1783 int Session::historySize() const
1784 {
1785     const HistoryType &currentHistory = historyType();
1786 
1787     if (currentHistory.isEnabled()) {
1788         if (currentHistory.isUnlimited()) {
1789             return -1;
1790         } else {
1791             return currentHistory.maximumLineCount();
1792         }
1793     } else {
1794         return 0;
1795     }
1796 }
1797 
1798 QString Session::profile()
1799 {
1800     return SessionManager::instance()->sessionProfile(this)->name();
1801 }
1802 
1803 void Session::setProfile(const QString &profileName)
1804 {
1805     const QList<Profile::Ptr> profiles = ProfileManager::instance()->allProfiles();
1806     for (const Profile::Ptr &profile : profiles) {
1807         if (profile->name() == profileName) {
1808             SessionManager::instance()->setSessionProfile(this, profile);
1809         }
1810     }
1811 }
1812 
1813 bool Session::copyInputToAllSessions()
1814 {
1815     if (auto c = controller()) {
1816         c->copyInputActions()->setCurrentItem(SessionController::CopyInputToAllTabsMode);
1817         c->copyInputToAllTabs();
1818         return true;
1819     }
1820 
1821     return false;
1822 }
1823 
1824 bool Session::copyInputToSessions(QList<int> sessionIds)
1825 {
1826     if (auto c = controller()) {
1827         auto sessions = new QList<Session *>();
1828         c->copyInputActions()->setCurrentItem(SessionController::CopyInputToSelectedTabsMode);
1829 
1830         for (auto sessionId : sessionIds) {
1831             if (auto session = SessionManager::instance()->idToSession(sessionId))
1832                 sessions->append(session);
1833             else
1834                 return false;
1835         }
1836 
1837         c->copyInputToSelectedTabs(sessions);
1838         return true;
1839     }
1840 
1841     return false;
1842 }
1843 
1844 bool Session::copyInputToNone()
1845 {
1846     if (auto c = controller()) {
1847         c->copyInputActions()->setCurrentItem(SessionController::CopyInputToNoneMode);
1848         c->copyInputToNone();
1849         return true;
1850     } else
1851         return false;
1852 }
1853 
1854 QList<int> Session::copyingSessions()
1855 {
1856     if (auto c = controller()) {
1857         if (auto copyToGroup = c->copyToGroup()) {
1858             QList<int> sessionIds;
1859 
1860             for (auto session : copyToGroup->sessions()) {
1861                 sessionIds.append(session->sessionId());
1862             }
1863 
1864             sessionIds.removeAll(sessionId());
1865             return sessionIds;
1866         }
1867     }
1868 
1869     return QList<int>();
1870 }
1871 
1872 QList<int> Session::feederSessions()
1873 {
1874     QList<int> feeders;
1875 
1876     for (auto session : SessionManager::instance()->sessions()) {
1877         if (session->copyingSessions().contains(sessionId()) && !feeders.contains(session->sessionId())) {
1878             feeders.append(session->sessionId());
1879         }
1880     }
1881 
1882     feeders.removeAll(sessionId());
1883     return feeders;
1884 }
1885 
1886 QString Session::getAllDisplayedText(bool removeTrailingEmptyLines)
1887 {
1888     return getAllDisplayedTextList(removeTrailingEmptyLines).join(QLatin1Char('\n'));
1889 }
1890 
1891 QStringList Session::getAllDisplayedTextList(bool removeTrailingEmptyLines)
1892 {
1893     auto screenWindow = _views.at(0)->screenWindow();
1894     if (removeTrailingEmptyLines) {
1895         auto lineproperties = screenWindow->getLineProperties();
1896         int lastNonemptyLine = screenWindow->windowLines();
1897 
1898         for (int i = lineproperties.size() - 1; i >= 0; --i) {
1899             if (lineproperties[i].length > 0) {
1900                 lastNonemptyLine = i;
1901                 break;
1902             }
1903         }
1904 
1905         return getDisplayedTextList(0, lastNonemptyLine);
1906     } else {
1907         return getDisplayedTextList(0, screenWindow->windowLines() - 1);
1908     }
1909 }
1910 
1911 QString Session::getDisplayedText(int startLineOffset, int endLineOffset)
1912 {
1913     return getDisplayedTextList(startLineOffset, endLineOffset).join(QLatin1Char('\n'));
1914 }
1915 
1916 QStringList Session::getDisplayedTextList(int startLineOffset, int endLineOffset)
1917 {
1918     auto screenWindow = _views.at(0)->screenWindow();
1919 
1920     if (startLineOffset < 0 || endLineOffset >= screenWindow->windowLines() || startLineOffset > endLineOffset) {
1921         return QStringList();
1922     }
1923 
1924     QStringList list;
1925     QTextStream stream;
1926     PlainTextDecoder decoder;
1927     int screenTopLineIndex = screenWindow->currentLine();
1928     int startLine = startLineOffset + screenTopLineIndex;
1929     int endLine = endLineOffset + screenTopLineIndex;
1930 
1931     for (int currLine = startLine; currLine <= endLine; ++currLine) {
1932         QString lineContent;
1933         stream.setString(&lineContent, QIODevice::ReadWrite);
1934         decoder.begin(&stream);
1935         screenWindow->screen()->writeLinesToStream(&decoder, currLine, currLine);
1936         decoder.end();
1937 
1938         if (lineContent.back() == QLatin1Char('\n')) {
1939             lineContent.removeLast();
1940         }
1941 
1942         list.append(lineContent);
1943     }
1944 
1945     return list;
1946 }
1947 
1948 int Session::foregroundProcessId()
1949 {
1950     int pid;
1951 
1952     bool ok = false;
1953     pid = getProcessInfo()->pid(&ok);
1954     if (!ok) {
1955         pid = -1;
1956     }
1957 
1958     return pid;
1959 }
1960 
1961 bool Session::isForegroundProcessActive()
1962 {
1963     const auto pid = processId();
1964     const auto fgid = _shellProcess->foregroundProcessGroup();
1965 
1966     // On FreeBSD, after exiting the shell, the foreground GID is
1967     // an invalid value, and the "shell" PID is 0. Those are not equal,
1968     // so the check below would return true.
1969     if (pid == 0) {
1970         return false;
1971     }
1972 
1973     // This check is wrong when Konsole is started with '-e cmd'
1974     // as there will only be one process.
1975     // See BKO 134581 for no popup when closing session
1976     return (pid != fgid);
1977 }
1978 
1979 QString Session::foregroundProcessName()
1980 {
1981     QString name;
1982 
1983     if (updateForegroundProcessInfo()) {
1984         bool ok = false;
1985         name = _foregroundProcessInfo->name(&ok);
1986         if (!ok) {
1987             name.clear();
1988         }
1989     }
1990 
1991     return name;
1992 }
1993 
1994 void Session::saveSession(KConfigGroup &group)
1995 {
1996     group.writePathEntry("WorkingDir", currentWorkingDirectory());
1997     group.writeEntry("LocalTab", tabTitleFormat(LocalTabTitle));
1998     group.writeEntry("RemoteTab", tabTitleFormat(RemoteTabTitle));
1999     group.writeEntry("TabColor", color().isValid() ? color().name(QColor::HexArgb) : QString());
2000     group.writeEntry("SessionGuid", _uniqueIdentifier.toString());
2001     group.writeEntry("Encoding", QString::fromUtf8(codec()));
2002 }
2003 
2004 void Session::restoreSession(KConfigGroup &group)
2005 {
2006     QString value;
2007 
2008     value = group.readPathEntry("WorkingDir", QString());
2009     if (!value.isEmpty()) {
2010         setInitialWorkingDirectory(value);
2011     }
2012     value = group.readEntry("LocalTab");
2013     if (!value.isEmpty()) {
2014         setTabTitleFormat(LocalTabTitle, value);
2015     }
2016     value = group.readEntry("RemoteTab");
2017     if (!value.isEmpty()) {
2018         setTabTitleFormat(RemoteTabTitle, value);
2019     }
2020     value = group.readEntry("TabColor");
2021     if (!value.isEmpty()) {
2022         setColor(QColor(value));
2023     }
2024     value = group.readEntry("SessionGuid");
2025     if (!value.isEmpty()) {
2026         _uniqueIdentifier = QUuid(value);
2027     }
2028     value = group.readEntry("Encoding");
2029     if (!value.isEmpty()) {
2030         setCodec(value.toUtf8());
2031     }
2032 }
2033 
2034 QString Session::validDirectory(const QString &dir) const
2035 {
2036     QString validDir = dir;
2037     if (validDir.isEmpty()) {
2038         validDir = QDir::currentPath();
2039     }
2040 
2041     const QFileInfo fi(validDir);
2042     if (!fi.exists() || !fi.isDir()) {
2043         validDir = QDir::homePath();
2044     }
2045 
2046     return validDir;
2047 }
2048 
2049 void Session::setPendingNotification(Session::Notification notification, bool enable)
2050 {
2051     if (enable != _activeNotifications.testFlag(notification)) {
2052         _activeNotifications.setFlag(notification, enable);
2053         Q_EMIT notificationsChanged(notification, enable);
2054     }
2055 }
2056 
2057 void Session::handleActivity()
2058 {
2059     // TODO: should this hardcoded interval be user configurable?
2060     const int activityMaskInSeconds = 15;
2061 
2062     TerminalDisplay *view = nullptr;
2063     if (!_views.isEmpty()) {
2064         view = _views.first();
2065     }
2066 
2067     if (_monitorActivity && !_notifiedActivity) {
2068         KNotification *notification =
2069             new KNotification(hasFocus() ? QStringLiteral("Activity") : QStringLiteral("ActivityHidden"), KNotification::CloseWhenWindowActivated);
2070         notification->setWindow(view->windowHandle());
2071         notification->setText(i18n("Activity in '%1' (Session '%2')", _displayTitle, _nameTitle));
2072         auto action = notification->addDefaultAction(i18n("Show session"));
2073         connect(action, &KNotificationAction::activated, this, [view, notification]() {
2074             view->notificationClicked(notification->xdgActivationToken());
2075         });
2076         notification->sendEvent();
2077 
2078         if (view->sessionController()->isMonitorOnce()) {
2079             view->sessionController()->actionCollection()->action(QStringLiteral("monitor-activity"))->setChecked(false);
2080         }
2081 
2082         // mask activity notification for a while to avoid flooding
2083         _notifiedActivity = true;
2084         _activityTimer->start(activityMaskInSeconds * 1000);
2085     }
2086 
2087     // reset the counter for monitoring continuous silence since there is activity
2088     if (_monitorSilence) {
2089         _silenceTimer->start(_silenceSeconds * 1000);
2090     }
2091 
2092     if (_monitorActivity) {
2093         setPendingNotification(Notification::Activity);
2094     }
2095 }
2096 
2097 bool Session::isReadOnly() const
2098 {
2099     return _readOnly;
2100 }
2101 
2102 void Session::setReadOnly(bool readOnly)
2103 {
2104     if (_readOnly != readOnly) {
2105         _readOnly = readOnly;
2106 
2107         // Needed to update the tab icons and all
2108         // attached views.
2109         Q_EMIT readOnlyChanged();
2110     }
2111 }
2112 bool Session::getSelectMode() const
2113 {
2114     return _selectMode;
2115 }
2116 
2117 void Session::setSelectMode(bool mode)
2118 {
2119     if (_selectMode != mode) {
2120         _selectMode = mode;
2121     }
2122 }
2123 
2124 void Session::setColor(const QColor &color)
2125 {
2126     _tabColor = color;
2127     Q_EMIT sessionAttributeChanged();
2128 }
2129 
2130 QColor Session::color() const
2131 {
2132     return _tabColor;
2133 }
2134 
2135 SessionController *Session::controller()
2136 {
2137     if (!_views.isEmpty())
2138         return _views.first()->sessionController();
2139 
2140     return nullptr;
2141 }
2142 
2143 #include "moc_Session.cpp"