File indexing completed on 2024-04-28 09:46:50

0001 /*
0002     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Own
0008 #include "Pty.h"
0009 
0010 #include "konsoledebug.h"
0011 
0012 // Qt
0013 #include <QStringList>
0014 #include <qplatformdefs.h>
0015 
0016 #ifndef Q_OS_WIN
0017 
0018 // System
0019 #include <csignal>
0020 #include <errno.h>
0021 #include <sys/ioctl.h> //ioctl() and TIOCSWINSZ
0022 #include <termios.h>
0023 
0024 // KDE
0025 #include <KPtyDevice>
0026 #include <kpty_version.h>
0027 
0028 using Konsole::Pty;
0029 
0030 Pty::Pty(QObject *aParent)
0031     : Pty(-1, aParent)
0032 {
0033 }
0034 
0035 Pty::Pty(int masterFd, QObject *aParent)
0036     : KPtyProcess(masterFd, aParent)
0037 {
0038     // Must call parent class child process modifier, as it sets file descriptors ...etc
0039     auto parentChildProcModifier = KPtyProcess::childProcessModifier();
0040     setChildProcessModifier([parentChildProcModifier = std::move(parentChildProcModifier)]() {
0041         if (parentChildProcModifier) {
0042             parentChildProcModifier();
0043         }
0044 
0045         // reset all signal handlers
0046         // this ensures that terminal applications respond to
0047         // signals generated via key sequences such as Ctrl+C
0048         // (which sends SIGINT)
0049         struct sigaction action;
0050         sigemptyset(&action.sa_mask);
0051         action.sa_handler = SIG_DFL;
0052         action.sa_flags = 0;
0053         for (int signal = 1; signal < NSIG; signal++) {
0054             sigaction(signal, &action, nullptr);
0055         }
0056     });
0057 
0058     _windowColumns = 0;
0059     _windowLines = 0;
0060     _windowWidth = 0;
0061     _windowHeight = 0;
0062     _eraseChar = 0;
0063     _xonXoff = true;
0064     _utf8 = true;
0065 
0066     setEraseChar(_eraseChar);
0067     setFlowControlEnabled(_xonXoff);
0068     setUtf8Mode(_utf8);
0069 
0070     setWindowSize(_windowColumns, _windowLines, _windowWidth, _windowHeight);
0071 
0072     setUseUtmp(true);
0073     setPtyChannels(KPtyProcess::AllChannels);
0074 
0075     connect(pty(), &KPtyDevice::readyRead, this, &Konsole::Pty::dataReceived);
0076 }
0077 
0078 Pty::~Pty() = default;
0079 
0080 void Pty::sendData(const QByteArray &data)
0081 {
0082     if (data.isEmpty()) {
0083         return;
0084     }
0085 
0086     if (pty()->write(data) == -1) {
0087         qCDebug(KonsoleDebug) << "Could not send input data to terminal process.";
0088         return;
0089     }
0090 }
0091 
0092 void Pty::dataReceived()
0093 {
0094     QByteArray data = pty()->readAll();
0095     if (data.isEmpty()) {
0096         return;
0097     }
0098 
0099     Q_EMIT receivedData(data.constData(), data.length());
0100 }
0101 
0102 void Pty::setWindowSize(int columns, int lines, int width, int height)
0103 {
0104     _windowColumns = columns;
0105     _windowLines = lines;
0106     _windowWidth = width;
0107     _windowHeight = height;
0108 
0109     if (pty()->masterFd() >= 0) {
0110         pty()->setWinSize(_windowLines, _windowColumns, _windowHeight, _windowWidth);
0111     }
0112 }
0113 
0114 QSize Pty::windowSize() const
0115 {
0116     return {_windowColumns, _windowLines};
0117 }
0118 
0119 QSize Pty::pixelSize() const
0120 {
0121     return {_windowWidth, _windowHeight};
0122 }
0123 
0124 void Pty::setFlowControlEnabled(bool enable)
0125 {
0126     _xonXoff = enable;
0127 
0128     if (pty()->masterFd() >= 0) {
0129         struct ::termios ttmode;
0130         pty()->tcGetAttr(&ttmode);
0131         if (enable) {
0132             ttmode.c_iflag |= (IXOFF | IXON);
0133         } else {
0134             ttmode.c_iflag &= ~(IXOFF | IXON);
0135         }
0136 
0137         if (!pty()->tcSetAttr(&ttmode)) {
0138             qCDebug(KonsoleDebug) << "Unable to set terminal attributes.";
0139         }
0140     }
0141 }
0142 
0143 bool Pty::flowControlEnabled() const
0144 {
0145     if (pty()->masterFd() >= 0) {
0146         struct ::termios ttmode;
0147         pty()->tcGetAttr(&ttmode);
0148         return ((ttmode.c_iflag & IXOFF) != 0U) && ((ttmode.c_iflag & IXON) != 0U);
0149     } else {
0150         qCDebug(KonsoleDebug) << "Unable to get flow control status, terminal not connected.";
0151         return _xonXoff;
0152     }
0153 }
0154 
0155 void Pty::setUtf8Mode(bool enable)
0156 {
0157 #if defined(IUTF8) // XXX not a reasonable place to check it.
0158     _utf8 = enable;
0159 
0160     if (pty()->masterFd() >= 0) {
0161         struct ::termios ttmode;
0162         pty()->tcGetAttr(&ttmode);
0163         if (enable) {
0164             ttmode.c_iflag |= IUTF8;
0165         } else {
0166             ttmode.c_iflag &= ~IUTF8;
0167         }
0168 
0169         if (!pty()->tcSetAttr(&ttmode)) {
0170             qCDebug(KonsoleDebug) << "Unable to set terminal attributes.";
0171         }
0172     }
0173 #else
0174     Q_UNUSED(enable)
0175 #endif
0176 }
0177 
0178 void Pty::setEraseChar(char eChar)
0179 {
0180     _eraseChar = eChar;
0181 
0182     if (pty()->masterFd() >= 0) {
0183         struct ::termios ttmode;
0184         pty()->tcGetAttr(&ttmode);
0185         ttmode.c_cc[VERASE] = eChar;
0186 
0187         if (!pty()->tcSetAttr(&ttmode)) {
0188             qCDebug(KonsoleDebug) << "Unable to set terminal attributes.";
0189         }
0190     }
0191 }
0192 
0193 char Pty::eraseChar() const
0194 {
0195     if (pty()->masterFd() >= 0) {
0196         struct ::termios ttyAttributes;
0197         pty()->tcGetAttr(&ttyAttributes);
0198         return ttyAttributes.c_cc[VERASE];
0199     } else {
0200         qCDebug(KonsoleDebug) << "Unable to get erase char attribute, terminal not connected.";
0201         return _eraseChar;
0202     }
0203 }
0204 
0205 void Pty::setInitialWorkingDirectory(const QString &dir)
0206 {
0207     QString pwd = dir;
0208 
0209     // remove trailing slash in the path when appropriate
0210     // example: /usr/share/icons/ ==> /usr/share/icons
0211     if (pwd.length() > 1 && pwd.endsWith(QLatin1Char('/'))) {
0212         pwd.chop(1);
0213     }
0214 
0215     setWorkingDirectory(pwd);
0216 
0217     // setting PWD to "." will cause problem for bash & zsh
0218     if (pwd != QLatin1String(".")) {
0219         setEnv(QStringLiteral("PWD"), pwd);
0220     }
0221 }
0222 
0223 void Pty::addEnvironmentVariables(const QStringList &environmentVariables)
0224 {
0225     bool isTermEnvAdded = false;
0226 
0227     for (const QString &pair : environmentVariables) {
0228         // split on the first '=' character
0229         const int separator = pair.indexOf(QLatin1Char('='));
0230 
0231         if (separator >= 0) {
0232             QString variable = pair.left(separator);
0233             QString value = pair.mid(separator + 1);
0234 
0235             setEnv(variable, value);
0236 
0237             if (variable == QLatin1String("TERM")) {
0238                 isTermEnvAdded = true;
0239             }
0240         }
0241     }
0242 
0243     // extra safeguard to make sure $TERM is always set
0244     if (!isTermEnvAdded) {
0245         setEnv(QStringLiteral("TERM"), QStringLiteral("xterm-256color"));
0246     }
0247 }
0248 
0249 int Pty::start(const QString &programName, const QStringList &programArguments, const QStringList &environmentList)
0250 {
0251     clearProgram();
0252 
0253     setProgram(programName, programArguments);
0254 
0255     addEnvironmentVariables(environmentList);
0256 
0257     // unless the LANGUAGE environment variable has been set explicitly
0258     // set it to a null string
0259     // this fixes the problem where KCatalog sets the LANGUAGE environment
0260     // variable during the application's startup to something which
0261     // differs from LANG,LC_* etc. and causes programs run from
0262     // the terminal to display messages in the wrong language
0263     //
0264     // this can happen if LANG contains a language which KDE
0265     // does not have a translation for
0266     //
0267     // BR:149300
0268     setEnv(QStringLiteral("LANGUAGE"), QString(), false /* do not overwrite existing value if any */);
0269 
0270     KProcess::start();
0271 
0272     if (waitForStarted()) {
0273         return 0;
0274     } else {
0275         return -1;
0276     }
0277 }
0278 
0279 void Pty::setWriteable(bool writeable)
0280 {
0281     QT_STATBUF sbuf;
0282     if (QT_STAT(pty()->ttyName(), &sbuf) == 0) {
0283         if (writeable) {
0284             if (::chmod(pty()->ttyName(), sbuf.st_mode | S_IWGRP) < 0) {
0285                 qCDebug(KonsoleDebug) << "Could not set writeable on " << pty()->ttyName();
0286             }
0287         } else {
0288             if (::chmod(pty()->ttyName(), sbuf.st_mode & ~(S_IWGRP | S_IWOTH)) < 0) {
0289                 qCDebug(KonsoleDebug) << "Could not unset writeable on " << pty()->ttyName();
0290             }
0291         }
0292     } else {
0293         qCDebug(KonsoleDebug) << "Could not stat " << pty()->ttyName();
0294     }
0295 }
0296 
0297 void Pty::closePty()
0298 {
0299     pty()->close();
0300 }
0301 
0302 int Pty::foregroundProcessGroup() const
0303 {
0304     const int master_fd = pty()->masterFd();
0305 
0306     if (master_fd >= 0) {
0307         int foregroundPid = tcgetpgrp(master_fd);
0308 
0309         if (foregroundPid != -1) {
0310             return foregroundPid;
0311         } else {
0312             qCWarning(KonsoleDebug, "Failed to get foreground process group id for %d: %s", master_fd, strerror(errno));
0313             return 0;
0314         }
0315     }
0316     qWarning(KonsoleDebug, "foregroundProcessGroup master_fd < 0");
0317 
0318     return 0;
0319 }
0320 
0321 #else // Windows backend
0322 
0323 #include "ptyqt/conptyprocess.h"
0324 
0325 using Konsole::Pty;
0326 
0327 Pty::Pty(QObject *aParent)
0328     : Pty(-1, aParent)
0329 {
0330 }
0331 
0332 Pty::Pty(int masterFd, QObject *aParent)
0333     : QObject(aParent)
0334 {
0335     Q_UNUSED(masterFd)
0336 
0337     m_proc = std::make_unique<ConPtyProcess>();
0338     if (!m_proc->isAvailable()) {
0339         m_proc.reset();
0340     }
0341 
0342     _windowColumns = 0;
0343     _windowLines = 0;
0344     _windowWidth = 0;
0345     _windowHeight = 0;
0346     _eraseChar = 0;
0347     _xonXoff = true;
0348     _utf8 = true;
0349 
0350     setEraseChar(_eraseChar);
0351 }
0352 
0353 Pty::~Pty() = default;
0354 
0355 void Pty::sendData(const QByteArray &data)
0356 {
0357     if (m_proc) {
0358         m_proc->write(data.constData(), data.length());
0359     }
0360 }
0361 
0362 void Pty::dataReceived()
0363 {
0364     if (m_proc) {
0365         auto data = m_proc->readAll();
0366         Q_EMIT receivedData(data.constData(), data.length());
0367     }
0368 }
0369 
0370 void Pty::setWindowSize(int columns, int lines, int, int)
0371 {
0372     if (m_proc && isRunning())
0373         m_proc->resize(columns, lines);
0374 }
0375 
0376 QSize Pty::windowSize() const
0377 {
0378     if (!m_proc) {
0379         return {};
0380     }
0381     auto s = m_proc->size();
0382     return QSize(s.first, s.second);
0383 }
0384 
0385 QSize Pty::pixelSize() const
0386 {
0387     return QSize();
0388 }
0389 
0390 void Pty::setFlowControlEnabled(bool enable)
0391 {
0392     _xonXoff = enable;
0393 }
0394 
0395 bool Pty::flowControlEnabled() const
0396 {
0397     return false;
0398 }
0399 
0400 void Pty::setUtf8Mode(bool)
0401 {
0402 }
0403 
0404 void Pty::setEraseChar(char eChar)
0405 {
0406     _eraseChar = eChar;
0407 }
0408 
0409 char Pty::eraseChar() const
0410 {
0411     return _eraseChar;
0412 }
0413 
0414 void Pty::setInitialWorkingDirectory(const QString & /*dir*/)
0415 {
0416 }
0417 
0418 void Pty::addEnvironmentVariables(const QStringList & /*environmentVariables*/)
0419 {
0420 }
0421 
0422 int Pty::start(const QString &program, const QStringList &arguments, const QString &workingDir, const QStringList &environment, int cols, int lines)
0423 {
0424     if (!m_proc || !m_proc->isAvailable()) {
0425         return -1;
0426     }
0427     bool res = m_proc->startProcess(program, arguments, workingDir, environment, cols, lines);
0428     if (!res) {
0429         return -1;
0430     } else {
0431         auto n = m_proc->notifier();
0432         connect(n, &QIODevice::readyRead, this, &Pty::dataReceived);
0433         connect(m_proc.get(), &IPtyProcess::exited, this, [this] {
0434             Q_EMIT finished(exitCode(), QProcess::NormalExit);
0435         });
0436         connect(n, &QIODevice::aboutToClose, this, [this] {
0437             Q_EMIT finished(exitCode(), QProcess::NormalExit);
0438         });
0439     }
0440     return 0;
0441 }
0442 
0443 void Pty::setWriteable(bool)
0444 {
0445 }
0446 
0447 void Pty::closePty()
0448 {
0449     if (m_proc) {
0450         m_proc->kill();
0451     }
0452 }
0453 
0454 int Pty::foregroundProcessGroup() const
0455 {
0456     return 0;
0457 }
0458 
0459 #endif
0460 
0461 #include "moc_Pty.cpp"