Files
orbithub/third_party/KodoTerm/src/PtyProcess_win.cpp
T

230 lines
6.9 KiB
C++

// SPDX-License-Identifier: MIT
// Author: Diego Iastrubni <diegoiast@gmail.com>
#include "PtyProcess_win.h"
#include <QDebug>
#include <QDir>
#include <vector>
// Define necessary types if building on older SDKs or mingw that might lack them
// But assuming standard modern environment.
class PtyProcessWin::ReaderThread : public QThread {
public:
ReaderThread(HANDLE hPipe, PtyProcessWin *parent) : m_hPipe(hPipe), m_parent(parent) {}
void run() override {
char buffer[4096];
DWORD bytesRead;
while (m_running) {
if (ReadFile(m_hPipe, buffer, sizeof(buffer), &bytesRead, NULL)) {
if (bytesRead > 0) {
QByteArray data(buffer, (int)bytesRead);
QMetaObject::invokeMethod(m_parent, "onReadThreadData", Qt::QueuedConnection,
Q_ARG(QByteArray, data));
} else {
// EOF (bytesRead == 0)
break;
}
} else {
DWORD err = GetLastError();
if (err == ERROR_BROKEN_PIPE || err == ERROR_HANDLE_EOF) {
break;
}
break;
}
}
}
void stop() { m_running = false; }
private:
HANDLE m_hPipe;
PtyProcessWin *m_parent;
bool m_running = true;
};
PtyProcessWin::PtyProcessWin(QObject *parent) : PtyProcess(parent) {
ZeroMemory(&m_pi, sizeof(PROCESS_INFORMATION));
m_hPC = INVALID_HANDLE_VALUE;
m_hPipeIn = INVALID_HANDLE_VALUE;
m_hPipeOut = INVALID_HANDLE_VALUE;
}
PtyProcessWin::~PtyProcessWin() {
kill(); // kill() now closes everything properly
}
bool PtyProcessWin::start(const QSize &size) {
if (m_program.isEmpty()) {
return false;
}
HANDLE hPipePTYIn = INVALID_HANDLE_VALUE;
HANDLE hPipePTYOut = INVALID_HANDLE_VALUE;
// Create pipes
if (!CreatePipe(&hPipePTYIn, &m_hPipeOut, NULL, 0)) {
return false;
}
if (!CreatePipe(&m_hPipeIn, &hPipePTYOut, NULL, 0)) {
return false;
}
// Create Pseudo Console
COORD origin = {(SHORT)size.width(), (SHORT)size.height()};
HRESULT hr = CreatePseudoConsole(origin, hPipePTYIn, hPipePTYOut, 0, &m_hPC);
// Close the sides we don't need
CloseHandle(hPipePTYIn);
CloseHandle(hPipePTYOut);
if (FAILED(hr)) {
return false;
}
// Prepare Startup Info
STARTUPINFOEX si;
ZeroMemory(&si, sizeof(STARTUPINFOEX));
si.StartupInfo.cb = sizeof(STARTUPINFOEX);
SIZE_T bytesRequired = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &bytesRequired);
si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, bytesRequired);
if (!si.lpAttributeList) {
return false;
}
if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &bytesRequired)) {
return false;
}
if (!UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
m_hPC, sizeof(HPCON), NULL, NULL)) {
return false;
}
// Command Line
QString programNative = QDir::toNativeSeparators(m_program);
QString cmd = programNative;
if (cmd.contains(' ')) {
cmd = "\"" + cmd + "\"";
}
for (const auto &arg : m_arguments) {
cmd += " " + arg; // Simple quoting might be needed
}
// Working directory
wchar_t *pWorkingDirectory = nullptr;
std::vector<wchar_t> workingDir;
if (!m_workingDirectory.isEmpty()) {
workingDir.resize(m_workingDirectory.length() + 1);
m_workingDirectory.toWCharArray(workingDir.data());
workingDir[m_workingDirectory.length()] = 0;
pWorkingDirectory = workingDir.data();
}
// Environment
std::vector<wchar_t> envBlock;
for (const auto &key : m_environment.keys()) {
QString entry = key + "=" + m_environment.value(key);
for (QChar c : entry) {
envBlock.push_back(c.unicode());
}
envBlock.push_back(0);
}
envBlock.push_back(0);
// Create Process
std::vector<wchar_t> cmdLine(cmd.length() + 1);
cmd.toWCharArray(cmdLine.data());
cmdLine[cmd.length()] = 0;
BOOL success = CreateProcessW(NULL, cmdLine.data(), NULL, NULL, FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT,
envBlock.data(), pWorkingDirectory, &si.StartupInfo, &m_pi);
// Cleanup attribute list
DeleteProcThreadAttributeList(si.lpAttributeList);
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
if (!success) {
return false;
}
// Start reader thread
m_readerThread = new ReaderThread(m_hPipeIn, this);
m_readerThread->start();
return true;
}
bool PtyProcessWin::start(const QString &program, const QStringList &arguments, const QSize &size) {
return PtyProcess::start(program, arguments, size);
}
void PtyProcessWin::write(const QByteArray &data) {
if (m_hPipeOut != INVALID_HANDLE_VALUE) {
DWORD bytesWritten;
WriteFile(m_hPipeOut, data.constData(), data.size(), &bytesWritten, NULL);
}
}
void PtyProcessWin::resize(const QSize &size) {
if (m_hPC != INVALID_HANDLE_VALUE) {
COORD origin = {(SHORT)size.width(), (SHORT)size.height()};
ResizePseudoConsole(m_hPC, origin);
}
}
void PtyProcessWin::kill() {
// First, close the pseudo console — this is the key fix
// It immediately unblocks any pending ReadFile on the pipes
if (m_hPC != INVALID_HANDLE_VALUE) {
ClosePseudoConsole(m_hPC);
m_hPC = INVALID_HANDLE_VALUE;
}
// Stop and clean up reader thread
if (m_readerThread) {
m_readerThread->stop();
// Close our read handle as a safety net (though ClosePseudoConsole already unblocked)
if (m_hPipeIn != INVALID_HANDLE_VALUE) {
CloseHandle(m_hPipeIn);
m_hPipeIn = INVALID_HANDLE_VALUE;
}
m_readerThread->wait(3000); // give it a reasonable timeout
if (m_readerThread->isRunning()) {
m_readerThread->terminate(); // last resort
m_readerThread->wait();
}
delete m_readerThread;
m_readerThread = nullptr;
}
// Close write pipe
if (m_hPipeOut != INVALID_HANDLE_VALUE) {
CloseHandle(m_hPipeOut);
m_hPipeOut = INVALID_HANDLE_VALUE;
}
// Terminate child process if still running
if (m_pi.hProcess) {
TerminateProcess(m_pi.hProcess, 1);
WaitForSingleObject(m_pi.hProcess, 5000);
CloseHandle(m_pi.hProcess);
CloseHandle(m_pi.hThread);
m_pi.hProcess = NULL;
m_pi.hThread = NULL;
}
}
bool PtyProcessWin::isRoot() const {
// Could check for elevation here
return false;
}
QString PtyProcessWin::foregroundProcessName() const { return QFileInfo(m_program).baseName(); }
void PtyProcessWin::onReadThreadData(const QByteArray &data) { emit readyRead(data); }