File indexing completed on 2024-05-19 05:28:19

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"