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

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 // System
0011 #include <csignal>
0012 #include <sys/ioctl.h> //ioctl() and TIOCSWINSZ
0013 #include <termios.h>
0014 
0015 // Qt
0016 #include <QDebug>
0017 #include <QStringList>
0018 #include <qplatformdefs.h>
0019 
0020 // KDE
0021 #include <KPtyDevice>
0022 #include <kpty_version.h>
0023 
0024 using Konsole::Pty;
0025 
0026 Pty::Pty(QObject *aParent)
0027     : Pty(-1, aParent)
0028 {
0029 }
0030 
0031 Pty::Pty(int masterFd, QObject *aParent)
0032     : KPtyProcess(masterFd, aParent)
0033 {
0034 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0035     // Must call parent class child process modifier, as it sets file descriptors ...etc
0036     auto parentChildProcModifier = KPtyProcess::childProcessModifier();
0037     setChildProcessModifier([parentChildProcModifier = std::move(parentChildProcModifier)]() {
0038         if (parentChildProcModifier) {
0039             parentChildProcModifier();
0040         }
0041 
0042         // reset all signal handlers
0043         // this ensures that terminal applications respond to
0044         // signals generated via key sequences such as Ctrl+C
0045         // (which sends SIGINT)
0046         struct sigaction action;
0047         sigemptyset(&action.sa_mask);
0048         action.sa_handler = SIG_DFL;
0049         action.sa_flags = 0;
0050         for (int signal = 1; signal < NSIG; signal++) {
0051             sigaction(signal, &action, nullptr);
0052         }
0053     });
0054 #endif
0055 
0056     _windowColumns = 0;
0057     _windowLines = 0;
0058     _windowWidth = 0;
0059     _windowHeight = 0;
0060     _eraseChar = 0;
0061     _xonXoff = true;
0062     _utf8 = true;
0063 
0064     setEraseChar(_eraseChar);
0065     setFlowControlEnabled(_xonXoff);
0066     setUtf8Mode(_utf8);
0067 
0068     setWindowSize(_windowColumns, _windowLines, _windowWidth, _windowHeight);
0069 
0070     setUseUtmp(true);
0071     setPtyChannels(KPtyProcess::AllChannels);
0072 
0073     connect(pty(), &KPtyDevice::readyRead, this, &Konsole::Pty::dataReceived);
0074 }
0075 
0076 Pty::~Pty() = default;
0077 
0078 void Pty::sendData(const QByteArray &data)
0079 {
0080     if (data.isEmpty()) {
0081         return;
0082     }
0083 
0084     if (pty()->write(data) == -1) {
0085         qDebug() << "Could not send input data to terminal process.";
0086         return;
0087     }
0088 }
0089 
0090 void Pty::dataReceived()
0091 {
0092     QByteArray data = pty()->readAll();
0093     if (data.isEmpty()) {
0094         return;
0095     }
0096 
0097     Q_EMIT receivedData(data.constData(), data.size());
0098 }
0099 
0100 void Pty::setWindowSize(int columns, int lines, int width, int height)
0101 {
0102     _windowColumns = columns;
0103     _windowLines = lines;
0104     _windowWidth = width;
0105     _windowHeight = height;
0106 
0107     if (pty()->masterFd() >= 0) {
0108 #if KPTY_VERSION >= QT_VERSION_CHECK(5, 93, 0)
0109         pty()->setWinSize(_windowLines, _windowColumns, _windowHeight, _windowWidth);
0110 #else
0111         struct winsize w;
0112         w.ws_xpixel = _windowWidth;
0113         w.ws_ypixel = _windowHeight;
0114         w.ws_col = _windowColumns;
0115         w.ws_row = _windowLines;
0116         ioctl(pty()->masterFd(), TIOCSWINSZ, &w);
0117 #endif
0118     }
0119 }
0120 
0121 QSize Pty::windowSize() const
0122 {
0123     return {_windowColumns, _windowLines};
0124 }
0125 
0126 QSize Pty::pixelSize() const
0127 {
0128     return {_windowWidth, _windowHeight};
0129 }
0130 
0131 void Pty::setFlowControlEnabled(bool enable)
0132 {
0133     _xonXoff = enable;
0134 
0135     if (pty()->masterFd() >= 0) {
0136         struct ::termios ttmode;
0137         pty()->tcGetAttr(&ttmode);
0138         if (enable) {
0139             ttmode.c_iflag |= (IXOFF | IXON);
0140         } else {
0141             ttmode.c_iflag &= ~(IXOFF | IXON);
0142         }
0143 
0144         if (!pty()->tcSetAttr(&ttmode)) {
0145             qDebug() << "Unable to set terminal attributes.";
0146         }
0147     }
0148 }
0149 
0150 bool Pty::flowControlEnabled() const
0151 {
0152     if (pty()->masterFd() >= 0) {
0153         struct ::termios ttmode;
0154         pty()->tcGetAttr(&ttmode);
0155         return ((ttmode.c_iflag & IXOFF) != 0U) && ((ttmode.c_iflag & IXON) != 0U);
0156     } else {
0157         qDebug() << "Unable to get flow control status, terminal not connected.";
0158         return _xonXoff;
0159     }
0160 }
0161 
0162 void Pty::setUtf8Mode(bool enable)
0163 {
0164 #if defined(IUTF8) // XXX not a reasonable place to check it.
0165     _utf8 = enable;
0166 
0167     if (pty()->masterFd() >= 0) {
0168         struct ::termios ttmode;
0169         pty()->tcGetAttr(&ttmode);
0170         if (enable) {
0171             ttmode.c_iflag |= IUTF8;
0172         } else {
0173             ttmode.c_iflag &= ~IUTF8;
0174         }
0175 
0176         if (!pty()->tcSetAttr(&ttmode)) {
0177             qDebug() << "Unable to set terminal attributes.";
0178         }
0179     }
0180 #else
0181     Q_UNUSED(enable)
0182 #endif
0183 }
0184 
0185 void Pty::setEraseChar(char eChar)
0186 {
0187     _eraseChar = eChar;
0188 
0189     if (pty()->masterFd() >= 0) {
0190         struct ::termios ttmode;
0191         pty()->tcGetAttr(&ttmode);
0192         ttmode.c_cc[VERASE] = eChar;
0193 
0194         if (!pty()->tcSetAttr(&ttmode)) {
0195             qDebug() << "Unable to set terminal attributes.";
0196         }
0197     }
0198 }
0199 
0200 char Pty::eraseChar() const
0201 {
0202     if (pty()->masterFd() >= 0) {
0203         struct ::termios ttyAttributes;
0204         pty()->tcGetAttr(&ttyAttributes);
0205         return ttyAttributes.c_cc[VERASE];
0206     } else {
0207         qDebug() << "Unable to get erase char attribute, terminal not connected.";
0208         return _eraseChar;
0209     }
0210 }
0211 
0212 void Pty::setInitialWorkingDirectory(const QString &dir)
0213 {
0214     QString pwd = dir;
0215 
0216     // remove trailing slash in the path when appropriate
0217     // example: /usr/share/icons/ ==> /usr/share/icons
0218     if (pwd.length() > 1 && pwd.endsWith(QLatin1Char('/'))) {
0219         pwd.chop(1);
0220     }
0221 
0222     setWorkingDirectory(pwd);
0223 
0224     // setting PWD to "." will cause problem for bash & zsh
0225     if (pwd != QLatin1String(".")) {
0226         setEnv(QStringLiteral("PWD"), pwd);
0227     }
0228 }
0229 
0230 void Pty::addEnvironmentVariables(const QStringList &environmentVariables)
0231 {
0232     bool isTermEnvAdded = false;
0233 
0234     for (const QString &pair : environmentVariables) {
0235         // split on the first '=' character
0236         const int separator = pair.indexOf(QLatin1Char('='));
0237 
0238         if (separator >= 0) {
0239             QString variable = pair.left(separator);
0240             QString value = pair.mid(separator + 1);
0241 
0242             setEnv(variable, value);
0243 
0244             if (variable == QLatin1String("TERM")) {
0245                 isTermEnvAdded = true;
0246             }
0247         }
0248     }
0249 
0250     // extra safeguard to make sure $TERM is always set
0251     if (!isTermEnvAdded) {
0252         setEnv(QStringLiteral("TERM"), QStringLiteral("xterm-256color"));
0253     }
0254 }
0255 
0256 int Pty::start(const QString &programName, const QStringList &programArguments, const QStringList &environmentList)
0257 {
0258     clearProgram();
0259 
0260     setProgram(programName, programArguments);
0261 
0262     addEnvironmentVariables(environmentList);
0263 
0264     // unless the LANGUAGE environment variable has been set explicitly
0265     // set it to a null string
0266     // this fixes the problem where KCatalog sets the LANGUAGE environment
0267     // variable during the application's startup to something which
0268     // differs from LANG,LC_* etc. and causes programs run from
0269     // the terminal to display messages in the wrong language
0270     //
0271     // this can happen if LANG contains a language which KDE
0272     // does not have a translation for
0273     //
0274     // BR:149300
0275     setEnv(QStringLiteral("LANGUAGE"), QString(), false /* do not overwrite existing value if any */);
0276 
0277     KProcess::start();
0278 
0279     if (waitForStarted()) {
0280         return 0;
0281     } else {
0282         return -1;
0283     }
0284 }
0285 
0286 void Pty::setWriteable(bool writeable)
0287 {
0288     QT_STATBUF sbuf;
0289     if (QT_STAT(pty()->ttyName(), &sbuf) == 0) {
0290         if (writeable) {
0291             if (::chmod(pty()->ttyName(), sbuf.st_mode | S_IWGRP) < 0) {
0292                 qDebug() << "Could not set writeable on " << pty()->ttyName();
0293             }
0294         } else {
0295             if (::chmod(pty()->ttyName(), sbuf.st_mode & ~(S_IWGRP | S_IWOTH)) < 0) {
0296                 qDebug() << "Could not unset writeable on " << pty()->ttyName();
0297             }
0298         }
0299     } else {
0300         qDebug() << "Could not stat " << pty()->ttyName();
0301     }
0302 }
0303 
0304 void Pty::closePty()
0305 {
0306     pty()->close();
0307 }
0308 
0309 int Pty::foregroundProcessGroup() const
0310 {
0311     const int master_fd = pty()->masterFd();
0312 
0313     if (master_fd >= 0) {
0314         int foregroundPid = tcgetpgrp(master_fd);
0315 
0316         if (foregroundPid != -1) {
0317             return foregroundPid;
0318         }
0319     }
0320 
0321     return 0;
0322 }
0323 
0324 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0325 void Pty::setupChildProcess()
0326 {
0327     KPtyProcess::setupChildProcess();
0328 
0329     // reset all signal handlers
0330     // this ensures that terminal applications respond to
0331     // signals generated via key sequences such as Ctrl+C
0332     // (which sends SIGINT)
0333     struct sigaction action;
0334     sigemptyset(&action.sa_mask);
0335     action.sa_handler = SIG_DFL;
0336     action.sa_flags = 0;
0337     for (int signal = 1; signal < NSIG; signal++) {
0338         sigaction(signal, &action, nullptr);
0339     }
0340 }
0341 #endif