/* Windows ConPTY-based Pty implementation using ptyqt. Replaces the POSIX kpty/kptyprocess stack on Windows. Only compiled on Windows via win32{} block in lib.pri — no Q_OS_WIN guard needed. */ #include "Pty.h" #include #include #include #include #include #include using namespace Konsole; Pty::Pty(QObject *parent) : QObject(parent) {} Pty::Pty(int /*ptyMasterFd*/, QObject *parent) : QObject(parent) {} Pty::~Pty() { if (_exitTimer) _exitTimer->stop(); if (_ptyProcess) { _ptyProcess->kill(); delete _ptyProcess; } } void Pty::kill() { if (_ptyProcess) _ptyProcess->kill(); } int Pty::start(const QString &program, const QStringList &arguments, const QStringList &environment, ulong /*winid*/, bool /*addToUtmp*/) { _ptyProcess = new ConPtyProcess(); if (!_ptyProcess->isAvailable()) { qWarning() << "Pty: ConPTY not available (requires Windows 10 build 1903+)"; delete _ptyProcess; _ptyProcess = nullptr; return -1; } // ConPtyProcess::startProcess requires an absolute path to an existing executable. // Resolve relative names (e.g. "powershell.exe") via SearchPathW. auto findExe = [](const QString &name) -> QString { wchar_t buf[MAX_PATH] = {}; std::wstring wname = name.toStdWString(); if (SearchPathW(nullptr, wname.c_str(), L".exe", MAX_PATH, buf, nullptr) > 0) return QString::fromWCharArray(buf); return {}; }; QString exec = program; if (!QFileInfo(exec).isAbsolute()) { exec = findExe(exec); if (exec.isEmpty() || !QFileInfo::exists(exec)) { exec = findExe(QStringLiteral("cmd.exe")); if (exec.isEmpty()) exec = QStringLiteral("C:\\Windows\\System32\\cmd.exe"); qWarning() << "Pty: could not resolve" << program << "— falling back to" << exec; } } connect(_ptyProcess->notifier(), &QIODevice::readyRead, this, &Pty::onReadyRead); // ConPtyProcess inherits the parent-process CWD. Temporarily change it // to the requested working directory and restore immediately after. wchar_t prevDir[MAX_PATH] = {}; const bool changedDir = !_workingDir.isEmpty() && GetCurrentDirectoryW(MAX_PATH, prevDir) != 0 && SetCurrentDirectoryW(_workingDir.toStdWString().c_str()); bool ok = _ptyProcess->startProcess(exec, environment, static_cast(_windowColumns), static_cast(_windowLines)); if (changedDir) SetCurrentDirectoryW(prevDir); if (!ok) { qWarning() << "Pty: failed to start" << exec << _ptyProcess->lastError(); delete _ptyProcess; _ptyProcess = nullptr; return -1; } // Poll every 500 ms to detect process exit (ConPTY has no exit signal) _exitTimer = new QTimer(this); _exitTimer->setInterval(500); connect(_exitTimer, &QTimer::timeout, this, &Pty::checkProcessExit); _exitTimer->start(); emit started(); return 0; } void Pty::sendData(const char *buffer, int length) { if (_ptyProcess && length > 0) _ptyProcess->write(QByteArray(buffer, length)); } void Pty::setWindowSize(int lines, int cols) { _windowLines = lines; _windowColumns = cols; if (_ptyProcess) _ptyProcess->resize(static_cast(cols), static_cast(lines)); } QSize Pty::windowSize() const { return {_windowColumns, _windowLines}; } int Pty::foregroundProcessGroup() const { return _ptyProcess ? static_cast(_ptyProcess->pid()) : 0; } QProcess::ProcessState Pty::state() const { if (!_ptyProcess || _ptyProcess->pid() == 0) return QProcess::NotRunning; HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, static_cast(_ptyProcess->pid())); if (!h) return QProcess::NotRunning; DWORD code = STILL_ACTIVE; GetExitCodeProcess(h, &code); CloseHandle(h); return (code == STILL_ACTIVE) ? QProcess::Running : QProcess::NotRunning; } qint64 Pty::processId() const { return _ptyProcess ? _ptyProcess->pid() : -1; } void Pty::onReadyRead() { if (!_ptyProcess) return; const QByteArray data = _ptyProcess->readAll(); if (!data.isEmpty()) emit receivedData(data.constData(), data.size()); } void Pty::checkProcessExit() { if (state() == QProcess::NotRunning) { _exitTimer->stop(); emit finished(0, QProcess::NormalExit); } }