File indexing completed on 2025-03-16 08:11:28
0001 /* 0002 This file is part of Konsole 0003 0004 SPDX-FileCopyrightText: 2006-2007 Robert Knight <robertknight@gmail.com> 0005 SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de> 0006 0007 Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 0011 This program is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 GNU General Public License for more details. 0015 0016 You should have received a copy of the GNU General Public License 0017 along with this program; if not, write to the Free Software 0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0019 02110-1301 USA. 0020 */ 0021 0022 // Own 0023 #include "Session.h" 0024 0025 // Standard 0026 #include <csignal> 0027 #include <cstdlib> 0028 0029 // Qt 0030 #include <QApplication> 0031 #include <QDir> 0032 #include <QFile> 0033 #include <QRegExp> 0034 #include <QRegularExpression> 0035 #include <QStringList> 0036 #include <QtDebug> 0037 0038 #include "Pty.h" 0039 // #include "kptyprocess.h" 0040 #include "ShellCommand.h" 0041 #include "TerminalDisplay.h" 0042 #include "Vt102Emulation.h" 0043 #include "kptydevice.h" 0044 0045 // QMLTermWidget 0046 #include <QQuickWindow> 0047 0048 using namespace Konsole; 0049 0050 int Session::lastSessionId = 0; 0051 0052 Session::Session(QObject *parent) 0053 : QObject(parent) 0054 , _shellProcess(nullptr) 0055 , _emulation(nullptr) 0056 , _monitorActivity(false) 0057 , _monitorSilence(false) 0058 , _notifiedActivity(false) 0059 , _autoClose(true) 0060 , _wantedClose(false) 0061 , _silenceSeconds(10) 0062 , _isTitleChanged(false) 0063 , _addToUtmp(false) // disabled by default because of a bug encountered on certain systems 0064 // which caused Konsole to hang when closing a tab and then opening a new 0065 // one. A 'QProcess destroyed while still running' warning was being 0066 // printed to the terminal. Likely a problem in KPty::logout() 0067 // or KPty::login() which uses a QProcess to start /usr/bin/utempter 0068 , _flowControl(true) 0069 , _fullScripting(false) 0070 , _sessionId(0) 0071 , _hasDarkBackground(false) 0072 , _foregroundPid(0) 0073 { 0074 _sessionId = ++lastSessionId; 0075 0076 // create teletype for I/O with shell process 0077 _shellProcess = std::make_unique<Pty>(); 0078 ptySlaveFd = _shellProcess->pty()->slaveFd(); 0079 0080 // create emulation backend 0081 _emulation = std::make_unique<Vt102Emulation>(); 0082 0083 connect(_emulation.get(), &Emulation::titleChanged, this, &Session::setUserTitle); 0084 connect(_emulation.get(), &Emulation::stateSet, this, &Session::activityStateSet); 0085 connect(_emulation.get(), &Emulation::changeTabTextColorRequest, this, &Session::changeTabTextColorRequest); 0086 connect(_emulation.get(), &Emulation::profileChangeCommandReceived, this, &Session::profileChangeCommandReceived); 0087 0088 connect(_emulation.get(), &Emulation::imageResizeRequest, this, &Session::onEmulationSizeChange); 0089 connect(_emulation.get(), &Emulation::imageSizeChanged, this, &Session::onViewSizeChange); 0090 connect(_emulation.get(), &Vt102Emulation::cursorChanged, this, &Session::cursorChanged); 0091 0092 // connect teletype to emulation backend 0093 _shellProcess->setUtf8Mode(_emulation->utf8()); 0094 0095 connect(_shellProcess.get(), &Pty::receivedData, this, &Session::onReceiveBlock); 0096 connect(_emulation.get(), &Emulation::sendData, _shellProcess.get(), &Pty::sendData); 0097 connect(_emulation.get(), &Emulation::useUtf8Request, _shellProcess.get(), &Pty::setUtf8Mode); 0098 0099 connect(_shellProcess.get(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, &Session::done); 0100 // not in kprocess anymore connect( _shellProcess,SIGNAL(done(int)), this, SLOT(done(int)) ); 0101 0102 // setup timer for monitoring session activity 0103 _monitorTimer = new QTimer(this); 0104 _monitorTimer->setSingleShot(true); 0105 connect(_monitorTimer, &QTimer::timeout, this, &Session::monitorTimerDone); 0106 } 0107 0108 WId Session::windowId() const 0109 { 0110 // On Qt5, requesting window IDs breaks QQuickWidget and the likes, 0111 // for example, see the following bug reports: 0112 // https://bugreports.qt.io/browse/QTBUG-40765 0113 // https://codereview.qt-project.org/#/c/94880/ 0114 return 0; 0115 } 0116 0117 void Session::setDarkBackground(bool darkBackground) 0118 { 0119 _hasDarkBackground = darkBackground; 0120 } 0121 bool Session::hasDarkBackground() const 0122 { 0123 return _hasDarkBackground; 0124 } 0125 bool Session::isRunning() const 0126 { 0127 return _shellProcess->state() == QProcess::Running; 0128 } 0129 0130 void Session::setCodec(QTextCodec *codec) const 0131 { 0132 emulation()->setCodec(codec); 0133 } 0134 0135 void Session::setProgram(const QString &program) 0136 { 0137 _program = ShellCommand::expand(program); 0138 } 0139 void Session::setInitialWorkingDirectory(const QString &dir) 0140 { 0141 _initialWorkingDir = ShellCommand::expand(dir); 0142 } 0143 void Session::setArguments(const QStringList &arguments) 0144 { 0145 _arguments = ShellCommand::expand(arguments); 0146 } 0147 0148 TerminalDisplay *Session::view() const 0149 { 0150 return _view; 0151 } 0152 0153 void Session::setView(TerminalDisplay *widget) 0154 { 0155 Q_ASSERT(!_view); 0156 _view = widget; 0157 0158 if (_emulation != nullptr) { 0159 // connect emulation - view signals and slots 0160 connect(widget, &TerminalDisplay::keyPressedSignal, _emulation.get(), &Emulation::sendKeyEvent); 0161 connect(widget, &TerminalDisplay::mouseSignal, _emulation.get(), &Emulation::sendMouseEvent); 0162 connect(widget, &TerminalDisplay::sendStringToEmu, _emulation.get(), [this](auto string) { 0163 _emulation->sendString(string); 0164 }); 0165 0166 // allow emulation to notify view when the foreground process 0167 // indicates whether or not it is interested in mouse signals 0168 connect(_emulation.get(), &Emulation::programUsesMouseChanged, widget, &TerminalDisplay::setUsesMouse); 0169 0170 widget->setUsesMouse(_emulation->programUsesMouse()); 0171 0172 connect(_emulation.get(), &Emulation::programBracketedPasteModeChanged, widget, &TerminalDisplay::setBracketedPasteMode); 0173 0174 widget->setBracketedPasteMode(_emulation->programBracketedPasteMode()); 0175 0176 widget->setScreenWindow(_emulation->createWindow()); 0177 } 0178 0179 // connect view signals and slots 0180 QObject::connect(widget, &TerminalDisplay::changedContentSizeSignal, this, &Session::onViewSizeChange); 0181 0182 QObject::connect(widget, &QObject::destroyed, this, &Session::viewDestroyed); 0183 // slot for close 0184 // QObject::connect(this, SIGNAL(finished()), widget, SLOT(close())); 0185 } 0186 0187 void Session::viewDestroyed(QObject *view) 0188 { 0189 TerminalDisplay *display = (TerminalDisplay *)view; 0190 Q_UNUSED(display) 0191 0192 Q_ASSERT(_view); 0193 0194 view = nullptr; 0195 } 0196 0197 void Session::removeView(TerminalDisplay *widget) 0198 { 0199 _view = nullptr; 0200 0201 disconnect(widget, nullptr, this, nullptr); 0202 0203 if (_emulation != nullptr) { 0204 // disconnect 0205 // - key presses signals from widget 0206 // - mouse activity signals from widget 0207 // - string sending signals from widget 0208 // 0209 // ... and any other signals connected in addView() 0210 disconnect(widget, nullptr, _emulation.get(), nullptr); 0211 0212 // disconnect state change signals emitted by emulation 0213 disconnect(_emulation.get(), nullptr, widget, nullptr); 0214 } 0215 0216 // close the session automatically when the last view is removed 0217 close(); 0218 } 0219 0220 void Session::run() 0221 { 0222 // Upon a KPty error, there is no description on what that error was... 0223 // Check to see if the given program is executable. 0224 0225 /* ok iam not exactly sure where _program comes from - however it was set to /bin/bash on my system 0226 * Thats bad for BSD as its /usr/local/bin/bash there - its also bad for arch as its /usr/bin/bash there too! 0227 * So i added a check to see if /bin/bash exists - if no then we use $SHELL - if that does not exist either, we fall back to /bin/sh 0228 * As far as i know /bin/sh exists on every unix system.. You could also just put some ifdef __FREEBSD__ here but i think these 2 filechecks are worth 0229 * their computing time on any system - especially with the problem on arch linux beeing there too. 0230 */ 0231 QString exec = QString::fromLocal8Bit(QFile::encodeName(_program)); 0232 // if 'exec' is not specified, fall back to default shell. if that 0233 // is not set then fall back to /bin/sh 0234 0235 // here we expect full path. If there is no fullpath let's expect it's 0236 // a custom shell (eg. python, etc.) available in the PATH. 0237 if (exec.startsWith(QLatin1Char('/')) || exec.isEmpty()) { 0238 const QString defaultShell{QLatin1String("/bin/sh")}; 0239 0240 QFile excheck(exec); 0241 if (exec.isEmpty() || !excheck.exists()) { 0242 exec = QString::fromLocal8Bit(qgetenv("SHELL")); 0243 } 0244 excheck.setFileName(exec); 0245 0246 if (exec.isEmpty() || !excheck.exists()) { 0247 qWarning() << "Neither default shell nor $SHELL is set to a correct path. Fallback to" << defaultShell; 0248 exec = defaultShell; 0249 } 0250 } 0251 0252 QString cwd = QDir::currentPath(); 0253 if (!_initialWorkingDir.isEmpty()) { 0254 _shellProcess->setWorkingDirectory(_initialWorkingDir); 0255 } else { 0256 _shellProcess->setWorkingDirectory(cwd); 0257 } 0258 0259 _shellProcess->setFlowControlEnabled(_flowControl); 0260 _shellProcess->setEraseChar(_emulation->eraseChar()); 0261 0262 // this is not strictly accurate use of the COLORFGBG variable. This does not 0263 // tell the terminal exactly which colors are being used, but instead approximates 0264 // the color scheme as "black on white" or "white on black" depending on whether 0265 // the background color is deemed dark or not 0266 QString backgroundColorHint = _hasDarkBackground ? QLatin1String("COLORFGBG=15;0") : QLatin1String("COLORFGBG=0;15"); 0267 0268 /* if we do all the checking if this shell exists then we use it ;) 0269 * Dont know about the arguments though.. maybe youll need some more checking im not sure 0270 * However this works on Arch and FreeBSD now. 0271 */ 0272 int result = _shellProcess->start(exec, _arguments, _environment << backgroundColorHint); 0273 0274 if (result < 0) { 0275 qDebug() << "CRASHED! result: " << result; 0276 return; 0277 } 0278 0279 _shellProcess->setWriteable(false); // We are reachable via kwrited. 0280 Q_EMIT started(); 0281 } 0282 0283 void Session::runEmptyPTY() 0284 { 0285 _shellProcess->setFlowControlEnabled(_flowControl); 0286 _shellProcess->setEraseChar(_emulation->eraseChar()); 0287 _shellProcess->setWriteable(false); 0288 0289 // disconnet send data from emulator to internal terminal process 0290 disconnect(_emulation.get(), &Emulation::sendData, _shellProcess.get(), &Pty::sendData); 0291 0292 Q_EMIT started(); 0293 } 0294 0295 void Session::setUserTitle(int what, const QString &caption) 0296 { 0297 // set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle ) 0298 bool modified = false; 0299 0300 // (btw: what=0 changes _userTitle and icon, what=1 only icon, what=2 only _nameTitle 0301 if ((what == 0) || (what == 2)) { 0302 _isTitleChanged = true; 0303 if (_userTitle != caption) { 0304 _userTitle = caption; 0305 modified = true; 0306 } 0307 } 0308 0309 if ((what == 0) || (what == 1)) { 0310 _isTitleChanged = true; 0311 if (_iconText != caption) { 0312 _iconText = caption; 0313 modified = true; 0314 } 0315 } 0316 0317 if (what == 11) { 0318 QString colorString = caption.section(QLatin1Char(';'), 0, 0); 0319 // qDebug() << __FILE__ << __LINE__ << ": setting background colour to " << colorString; 0320 QColor backColor = QColor(colorString); 0321 if (backColor.isValid()) { // change color via \033]11;Color\007 0322 if (backColor != _modifiedBackground) { 0323 _modifiedBackground = backColor; 0324 0325 // bail out here until the code to connect the terminal display 0326 // to the changeBackgroundColor() signal has been written 0327 // and tested - just so we don't forget to do this. 0328 Q_ASSERT(0); 0329 0330 Q_EMIT changeBackgroundColorRequest(backColor); 0331 } 0332 } 0333 } 0334 0335 if (what == 30) { 0336 _isTitleChanged = true; 0337 if (_nameTitle != caption) { 0338 setTitle(Session::NameRole, caption); 0339 return; 0340 } 0341 } 0342 0343 if (what == 31) { 0344 QString cwd = caption; 0345 cwd = cwd.replace(QRegularExpression(QLatin1String("^~")), QDir::homePath()); 0346 Q_EMIT openUrlRequest(cwd); 0347 } 0348 0349 // change icon via \033]32;Icon\007 0350 if (what == 32) { 0351 _isTitleChanged = true; 0352 if (_iconName != caption) { 0353 _iconName = caption; 0354 0355 modified = true; 0356 } 0357 } 0358 0359 if (what == 50) { 0360 Q_EMIT profileChangeCommandReceived(caption); 0361 return; 0362 } 0363 0364 if (modified) { 0365 Q_EMIT titleChanged(); 0366 } 0367 } 0368 0369 QString Session::userTitle() const 0370 { 0371 return _userTitle; 0372 } 0373 0374 void Session::setTabTitleFormat(TabTitleContext context, const QString &format) 0375 { 0376 if (context == LocalTabTitle) { 0377 _localTabTitleFormat = format; 0378 } else if (context == RemoteTabTitle) { 0379 _remoteTabTitleFormat = format; 0380 } 0381 } 0382 QString Session::tabTitleFormat(TabTitleContext context) const 0383 { 0384 if (context == LocalTabTitle) { 0385 return _localTabTitleFormat; 0386 } else if (context == RemoteTabTitle) { 0387 return _remoteTabTitleFormat; 0388 } 0389 0390 return QString(); 0391 } 0392 0393 void Session::monitorTimerDone() 0394 { 0395 // FIXME: The idea here is that the notification popup will appear to tell the user than output from 0396 // the terminal has stopped and the popup will disappear when the user activates the session. 0397 // 0398 // This breaks with the addition of multiple views of a session. The popup should disappear 0399 // when any of the views of the session becomes active 0400 0401 // FIXME: Make message text for this notification and the activity notification more descriptive. 0402 if (_monitorSilence) { 0403 Q_EMIT silence(); 0404 Q_EMIT stateChanged(NOTIFYSILENCE); 0405 } else { 0406 Q_EMIT stateChanged(NOTIFYNORMAL); 0407 } 0408 0409 _notifiedActivity = false; 0410 } 0411 0412 void Session::activityStateSet(int state) 0413 { 0414 if (state == NOTIFYBELL) { 0415 Q_EMIT bellRequest(tr("Bell in session '%1'").arg(_nameTitle)); 0416 } else if (state == NOTIFYACTIVITY) { 0417 if (_monitorSilence) { 0418 _monitorTimer->start(_silenceSeconds * 1000); 0419 } 0420 0421 if (_monitorActivity) { 0422 // FIXME: See comments in Session::monitorTimerDone() 0423 if (!_notifiedActivity) { 0424 _notifiedActivity = true; 0425 Q_EMIT activity(); 0426 } 0427 } 0428 } 0429 0430 if (state == NOTIFYACTIVITY && !_monitorActivity) { 0431 state = NOTIFYNORMAL; 0432 } 0433 if (state == NOTIFYSILENCE && !_monitorSilence) { 0434 state = NOTIFYNORMAL; 0435 } 0436 0437 Q_EMIT stateChanged(state); 0438 } 0439 0440 void Session::onViewSizeChange(int /*height*/, int /*width*/) 0441 { 0442 updateTerminalSize(); 0443 } 0444 void Session::onEmulationSizeChange(QSize size) 0445 { 0446 setSize(size); 0447 } 0448 0449 void Session::updateTerminalSize() 0450 { 0451 // backend emulation must have a _terminal of at least 1 column x 1 line in size 0452 _emulation->setImageSize(_view->lines(), _view->columns()); 0453 _shellProcess->setWindowSize(_view->columns(), _view->lines(), _view->width(), _view->height()); 0454 } 0455 0456 void Session::refresh() 0457 { 0458 // attempt to get the shell process to redraw the display 0459 // 0460 // this requires the program running in the shell 0461 // to cooperate by sending an update in response to 0462 // a window size change 0463 // 0464 // the window size is changed twice, first made slightly larger and then 0465 // resized back to its normal size so that there is actually a change 0466 // in the window size (some shells do nothing if the 0467 // new and old sizes are the same) 0468 // 0469 // if there is a more 'correct' way to do this, please 0470 // send an email with method or patches to konsole-devel@kde.org 0471 0472 const QSize existingSize = _shellProcess->windowSize(); 0473 const QSize existingPixelSize = _shellProcess->pixelSize(); 0474 _shellProcess->setWindowSize(existingSize.height(), existingSize.width() + 1, existingPixelSize.height(), existingPixelSize.width()); 0475 _shellProcess->setWindowSize(existingSize.height(), existingSize.width(), existingPixelSize.height(), existingPixelSize.width()); 0476 } 0477 0478 bool Session::sendSignal(int signal) 0479 { 0480 int result = ::kill(static_cast<pid_t>(_shellProcess->processId()), signal); 0481 0482 if (result == 0) { 0483 _shellProcess->waitForFinished(); 0484 return true; 0485 } else 0486 return false; 0487 } 0488 0489 void Session::close() 0490 { 0491 _autoClose = true; 0492 _wantedClose = true; 0493 if (_shellProcess->state() != QProcess::Running || !sendSignal(SIGHUP)) { 0494 // Forced close. 0495 QTimer::singleShot(1, this, &Session::finished); 0496 } 0497 } 0498 0499 void Session::sendText(const QString &text) const 0500 { 0501 _emulation->sendText(text); 0502 } 0503 0504 void Session::sendKeyEvent(QKeyEvent *e) const 0505 { 0506 _emulation->sendKeyEvent(e, false); 0507 } 0508 0509 Session::~Session() = default; 0510 0511 void Session::setProfileKey(const QString &key) 0512 { 0513 _profileKey = key; 0514 Q_EMIT profileChanged(key); 0515 } 0516 0517 QString Session::profileKey() const 0518 { 0519 return _profileKey; 0520 } 0521 0522 void Session::done(int exitStatus) 0523 { 0524 if (!_autoClose) { 0525 _userTitle = QString::fromLatin1("This session is done. Finished"); 0526 Q_EMIT titleChanged(); 0527 return; 0528 } 0529 0530 // message is not being used. But in the original kpty.cpp file 0531 // (https://cgit.kde.org/kpty.git/) it's part of a notification. 0532 // So, we make it translatable, hoping that in the future it will 0533 // be used in some kind of notification. 0534 QString message; 0535 if (!_wantedClose || exitStatus != 0) { 0536 if (_shellProcess->exitStatus() == QProcess::NormalExit) { 0537 message = tr("Session '%1' exited with status %2.").arg(_nameTitle).arg(exitStatus); 0538 } else { 0539 message = tr("Session '%1' crashed.").arg(_nameTitle); 0540 } 0541 } 0542 0543 if (!_wantedClose && _shellProcess->exitStatus() != QProcess::NormalExit) 0544 message = tr("Session '%1' exited unexpectedly.").arg(_nameTitle); 0545 else 0546 Q_EMIT finished(); 0547 } 0548 0549 Emulation *Session::emulation() const 0550 { 0551 return _emulation.get(); 0552 } 0553 0554 QString Session::keyBindings() const 0555 { 0556 return _emulation->keyBindings(); 0557 } 0558 0559 QStringList Session::environment() const 0560 { 0561 return _environment; 0562 } 0563 0564 void Session::setEnvironment(const QStringList &environment) 0565 { 0566 _environment = environment; 0567 } 0568 0569 int Session::sessionId() const 0570 { 0571 return _sessionId; 0572 } 0573 0574 void Session::setKeyBindings(const QString &id) 0575 { 0576 _emulation->setKeyBindings(id); 0577 } 0578 0579 void Session::setTitle(TitleRole role, const QString &newTitle) 0580 { 0581 if (title(role) != newTitle) { 0582 if (role == NameRole) { 0583 _nameTitle = newTitle; 0584 } else if (role == DisplayedTitleRole) { 0585 _displayTitle = newTitle; 0586 } 0587 0588 Q_EMIT titleChanged(); 0589 } 0590 } 0591 0592 QString Session::title(TitleRole role) const 0593 { 0594 if (role == NameRole) { 0595 return _nameTitle; 0596 } else if (role == DisplayedTitleRole) { 0597 return _displayTitle; 0598 } else { 0599 return QString(); 0600 } 0601 } 0602 0603 void Session::setIconName(const QString &iconName) 0604 { 0605 if (iconName != _iconName) { 0606 _iconName = iconName; 0607 Q_EMIT titleChanged(); 0608 } 0609 } 0610 0611 void Session::setIconText(const QString &iconText) 0612 { 0613 _iconText = iconText; 0614 // kDebug(1211)<<"Session setIconText " << _iconText; 0615 } 0616 0617 QString Session::iconName() const 0618 { 0619 return _iconName; 0620 } 0621 0622 QString Session::iconText() const 0623 { 0624 return _iconText; 0625 } 0626 0627 bool Session::isTitleChanged() const 0628 { 0629 return _isTitleChanged; 0630 } 0631 0632 void Session::setHistoryType(const HistoryType &hType) 0633 { 0634 _emulation->setHistory(hType); 0635 } 0636 0637 const HistoryType &Session::historyType() const 0638 { 0639 return _emulation->history(); 0640 } 0641 0642 void Session::clearHistory() 0643 { 0644 _emulation->clearHistory(); 0645 } 0646 0647 QStringList Session::arguments() const 0648 { 0649 return _arguments; 0650 } 0651 0652 QString Session::program() const 0653 { 0654 return _program; 0655 } 0656 0657 // unused currently 0658 bool Session::isMonitorActivity() const 0659 { 0660 return _monitorActivity; 0661 } 0662 // unused currently 0663 bool Session::isMonitorSilence() const 0664 { 0665 return _monitorSilence; 0666 } 0667 0668 void Session::setMonitorActivity(bool _monitor) 0669 { 0670 _monitorActivity = _monitor; 0671 _notifiedActivity = false; 0672 0673 activityStateSet(NOTIFYNORMAL); 0674 } 0675 0676 void Session::setMonitorSilence(bool _monitor) 0677 { 0678 if (_monitorSilence == _monitor) { 0679 return; 0680 } 0681 0682 _monitorSilence = _monitor; 0683 if (_monitorSilence) { 0684 _monitorTimer->start(_silenceSeconds * 1000); 0685 } else { 0686 _monitorTimer->stop(); 0687 } 0688 0689 activityStateSet(NOTIFYNORMAL); 0690 } 0691 0692 void Session::setMonitorSilenceSeconds(int seconds) 0693 { 0694 _silenceSeconds = seconds; 0695 if (_monitorSilence) { 0696 _monitorTimer->start(_silenceSeconds * 1000); 0697 } 0698 } 0699 0700 void Session::setAddToUtmp(bool set) 0701 { 0702 _addToUtmp = set; 0703 } 0704 0705 void Session::setFlowControlEnabled(bool enabled) 0706 { 0707 if (_flowControl == enabled) { 0708 return; 0709 } 0710 0711 _flowControl = enabled; 0712 0713 if (_shellProcess) { 0714 _shellProcess->setFlowControlEnabled(_flowControl); 0715 } 0716 0717 Q_EMIT flowControlEnabledChanged(enabled); 0718 } 0719 bool Session::flowControlEnabled() const 0720 { 0721 return _flowControl; 0722 } 0723 0724 void Session::onReceiveBlock(const char *buf, int len) 0725 { 0726 _emulation->receiveData(buf, len); 0727 Q_EMIT receivedData(QString::fromLatin1(buf, len)); 0728 } 0729 0730 QSize Session::size() 0731 { 0732 return _emulation->imageSize(); 0733 } 0734 0735 void Session::setSize(const QSize &size) 0736 { 0737 if ((size.width() <= 1) || (size.height() <= 1)) { 0738 return; 0739 } 0740 0741 Q_EMIT resizeRequest(size); 0742 } 0743 int Session::foregroundProcessId() const 0744 { 0745 return _shellProcess->foregroundProcessGroup(); 0746 } 0747 0748 QString Session::foregroundProcessName() 0749 { 0750 QString name; 0751 0752 if (updateForegroundProcessInfo()) { 0753 bool ok = false; 0754 name = _foregroundProcessInfo->name(&ok); 0755 if (!ok) 0756 name.clear(); 0757 } 0758 0759 return name; 0760 } 0761 0762 QString Session::currentDir() 0763 { 0764 QString path; 0765 if (updateForegroundProcessInfo()) { 0766 bool ok = false; 0767 path = _foregroundProcessInfo->currentDir(&ok); 0768 if (!ok) 0769 path.clear(); 0770 } 0771 return path; 0772 } 0773 0774 bool Session::updateForegroundProcessInfo() 0775 { 0776 Q_ASSERT(_shellProcess); 0777 0778 const int foregroundPid = _shellProcess->foregroundProcessGroup(); 0779 if (foregroundPid != _foregroundPid) { 0780 _foregroundProcessInfo.reset(); 0781 _foregroundProcessInfo = ProcessInfo::newInstance(foregroundPid); 0782 _foregroundPid = foregroundPid; 0783 } 0784 0785 if (_foregroundProcessInfo) { 0786 _foregroundProcessInfo->update(); 0787 return _foregroundProcessInfo->isValid(); 0788 } else { 0789 return false; 0790 } 0791 } 0792 0793 int Session::processId() const 0794 { 0795 return static_cast<int>(_shellProcess->processId()); 0796 } 0797 int Session::getPtySlaveFd() const 0798 { 0799 return ptySlaveFd; 0800 } 0801 0802 SessionGroup::SessionGroup() 0803 : _masterMode(0) 0804 { 0805 } 0806 SessionGroup::~SessionGroup() 0807 { 0808 // disconnect all 0809 connectAll(false); 0810 } 0811 int SessionGroup::masterMode() const 0812 { 0813 return _masterMode; 0814 } 0815 QList<Session *> SessionGroup::sessions() const 0816 { 0817 return _sessions.keys(); 0818 } 0819 bool SessionGroup::masterStatus(Session *session) const 0820 { 0821 return _sessions[session]; 0822 } 0823 0824 void SessionGroup::addSession(Session *session) 0825 { 0826 _sessions.insert(session, false); 0827 0828 const auto masterSessions = masters(); 0829 for (const auto master : masterSessions) { 0830 connectPair(master, session); 0831 } 0832 } 0833 0834 void SessionGroup::removeSession(Session *session) 0835 { 0836 setMasterStatus(session, false); 0837 0838 const auto masterSessions = masters(); 0839 for (const auto master : masterSessions) { 0840 disconnectPair(master, session); 0841 } 0842 0843 _sessions.remove(session); 0844 } 0845 0846 void SessionGroup::setMasterMode(int mode) 0847 { 0848 _masterMode = mode; 0849 0850 connectAll(false); 0851 connectAll(true); 0852 } 0853 0854 QList<Session *> SessionGroup::masters() const 0855 { 0856 return _sessions.keys(true); 0857 } 0858 0859 void SessionGroup::connectAll(bool connect) 0860 { 0861 const auto masterSessions = masters(); 0862 for (const auto master : masterSessions) { 0863 const auto other = _sessions.keys(); 0864 0865 for (const auto other : other) { 0866 if (other != master) { 0867 if (connect) { 0868 connectPair(master, other); 0869 } else { 0870 disconnectPair(master, other); 0871 } 0872 } 0873 } 0874 } 0875 } 0876 0877 void SessionGroup::setMasterStatus(Session *session, bool master) 0878 { 0879 bool wasMaster = _sessions[session]; 0880 _sessions[session] = master; 0881 0882 if (wasMaster == master) { 0883 return; 0884 } 0885 0886 const auto otherSessions = _sessions.keys(); 0887 for (const auto other : otherSessions) { 0888 if (other != session) { 0889 if (master) { 0890 connectPair(session, other); 0891 } else { 0892 disconnectPair(session, other); 0893 } 0894 } 0895 } 0896 } 0897 0898 void SessionGroup::connectPair(Session *master, Session *other) const 0899 { 0900 // qDebug() << k_funcinfo; 0901 0902 if (_masterMode & CopyInputToAll) { 0903 qDebug() << "Connection session " << master->nameTitle() << "to" << other->nameTitle(); 0904 0905 connect(master->emulation(), &Emulation::sendData, other->emulation(), &Emulation::sendString); 0906 } 0907 } 0908 void SessionGroup::disconnectPair(Session *master, Session *other) const 0909 { 0910 // qDebug() << k_funcinfo; 0911 0912 if (_masterMode & CopyInputToAll) { 0913 qDebug() << "Disconnecting session " << master->nameTitle() << "from" << other->nameTitle(); 0914 0915 disconnect(master->emulation(), &Emulation::sendData, other->emulation(), &Emulation::sendString); 0916 } 0917 } 0918 0919 // #include "moc_Session.cpp"