Files
ptyqt/core/conptyprocess.h
lovecraft ea4dd16e5a Windows: fix ConPTY availability and environment inheritance
Remove TOO_OLD_WINSDK guard that blocked ConPTY on all Windows
builds, including Windows 11. The guard was set whenever the MinGW
SDK lacked PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, but the fallback
definitions provided in the header are correct and CreatePseudoConsole
is resolved at runtime via GetProcAddress regardless.

Fix environment block construction in startProcess: previously built
a partial env block even when the caller only supplied one override
var (e.g. COLORFGBG), stripping PATH and all system variables from
the child process. Now merges the full current process environment
via GetEnvironmentStringsW() and applies caller-supplied vars on top.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 11:10:33 +02:00

160 lines
4.3 KiB
C++

#ifndef CONPTYPROCESS_H
#define CONPTYPROCESS_H
#include "iptyprocess.h"
#include <windows.h>
#include <process.h>
#include <QLibrary>
#include <stdio.h>
#include <QMutex>
#include <QTimer>
#include <QThread>
//Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17733
//Just for compile, ConPty doesn't work with Windows SDK < 17733
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \
ProcThreadAttributeValue(22, FALSE, TRUE, FALSE)
typedef VOID* HPCON;
#endif
template <typename T>
std::vector<T> vectorFromString(const std::basic_string<T> &str)
{
return std::vector<T>(str.begin(), str.end());
}
//ConPTY available only on Windows 10 releazed after 1903 (19H1) Windows release
class WindowsContext
{
public:
typedef HRESULT (*CreatePseudoConsolePtr)(
COORD size, // ConPty Dimensions
HANDLE hInput, // ConPty Input
HANDLE hOutput, // ConPty Output
DWORD dwFlags, // ConPty Flags
HPCON* phPC); // ConPty Reference
typedef HRESULT (*ResizePseudoConsolePtr)(HPCON hPC, COORD size);
typedef VOID (*ClosePseudoConsolePtr)(HPCON hPC);
WindowsContext()
: createPseudoConsole(nullptr)
, resizePseudoConsole(nullptr)
, closePseudoConsole(nullptr)
{
}
bool init()
{
//already initialized
if (createPseudoConsole)
return true;
//try to load symbols from library
//if it fails -> we can't use ConPty API
HANDLE kernel32Handle = LoadLibraryExW(L"kernel32.dll", 0, 0);
if (kernel32Handle != nullptr)
{
createPseudoConsole = (CreatePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "CreatePseudoConsole");
resizePseudoConsole = (ResizePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ResizePseudoConsole");
closePseudoConsole = (ClosePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ClosePseudoConsole");
if (createPseudoConsole == NULL || resizePseudoConsole == NULL || closePseudoConsole == NULL)
{
m_lastError = QString("WindowsContext/ConPty error: %1").arg("Invalid on load API functions");
return false;
}
}
else
{
m_lastError = QString("WindowsContext/ConPty error: %1").arg("Unable to load kernel32");
return false;
}
return true;
}
QString lastError()
{
return m_lastError;
}
public:
//vars
CreatePseudoConsolePtr createPseudoConsole;
ResizePseudoConsolePtr resizePseudoConsole;
ClosePseudoConsolePtr closePseudoConsole;
private:
QString m_lastError;
};
class PtyBuffer : public QIODevice
{
friend class ConPtyProcess;
Q_OBJECT
public:
PtyBuffer() { }
~PtyBuffer() { }
//just empty realization, we need only 'readyRead' signal of this class
qint64 readData(char *data, qint64 maxlen) { return 0; }
qint64 writeData(const char *data, qint64 len) { return 0; }
bool isSequential() { return true; }
qint64 bytesAvailable() { return m_readBuffer.size(); }
qint64 size() { return m_readBuffer.size(); }
void emitReadyRead()
{
//for emit signal from PtyBuffer own thread
QTimer::singleShot(1, this, [this]()
{
emit readyRead();
});
}
private:
QByteArray m_readBuffer;
};
class ConPtyProcess : public IPtyProcess
{
public:
ConPtyProcess();
~ConPtyProcess();
bool startProcess(const QString &shellPath, QStringList environment, qint16 cols, qint16 rows);
bool resize(qint16 cols, qint16 rows);
bool kill();
PtyType type();
QString dumpDebugInfo();
virtual QIODevice *notifier();
virtual QByteArray readAll();
virtual qint64 write(const QByteArray &byteArray);
bool isAvailable();
void moveToThread(QThread *targetThread);
private:
HRESULT createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut, qint16 cols, qint16 rows);
HRESULT initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC);
private:
WindowsContext m_winContext;
HPCON m_ptyHandler;
HANDLE m_hPipeIn, m_hPipeOut;
QThread *m_readThread;
QMutex m_bufferMutex;
PtyBuffer m_buffer;
};
#endif // CONPTYPROCESS_H