Files
qmltermwidget/lib/Pty_win.cpp
T
2026-05-06 10:59:58 +02:00

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);
}
}