Files
qmltermwidget/lib/Pty_win.cpp
T
2026-05-04 22:25:48 +02:00

156 lines
4.3 KiB
C++

/*
Windows ConPTY-based Pty implementation using ptyqt.
Replaces the POSIX kpty/kptyprocess stack on Windows.
*/
#if defined(Q_OS_WIN)
#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 PATH.
QString exec = program;
if (!QFileInfo(exec).isAbsolute())
exec = QProcess::findExecutable(exec);
if (exec.isEmpty() || !QFileInfo::exists(exec)) {
// Final fallback: cmd.exe is always available
exec = QProcess::findExecutable(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);
}
}
#endif // Q_OS_WIN