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"