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 ¤tHistory = 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"