161 lines
4.6 KiB
C++
161 lines
4.6 KiB
C++
/*
|
|
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 <conptyprocess.h>
|
|
#include <QProcess>
|
|
#include <QFileInfo>
|
|
#include <QIODevice>
|
|
#include <QtDebug>
|
|
#include <windows.h>
|
|
|
|
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<qint16>(_windowColumns),
|
|
static_cast<qint16>(_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<qint16>(cols), static_cast<qint16>(lines));
|
|
}
|
|
|
|
QSize Pty::windowSize() const
|
|
{
|
|
return {_windowColumns, _windowLines};
|
|
}
|
|
|
|
int Pty::foregroundProcessGroup() const
|
|
{
|
|
return _ptyProcess ? static_cast<int>(_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<DWORD>(_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);
|
|
}
|
|
}
|