File indexing completed on 2025-03-16 08:11:26
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