File indexing completed on 2024-04-28 05:50:45

0001 /*
0002     SPDX-FileCopyrightText: 2019 Vitaly Petrov <v31337@gmail.com>
0003     SPDX-License-Identifier: MIT
0004 */
0005 #ifndef CONPTYPROCESS_H
0006 #define CONPTYPROCESS_H
0007 
0008 #include "iptyprocess.h"
0009 #include <QLibrary>
0010 #include <QMutex>
0011 #include <QThread>
0012 #include <QTimer>
0013 #include <process.h>
0014 #include <stdio.h>
0015 #include <windows.h>
0016 
0017 // Taken from the RS5 Windows SDK, but redefined here in case we're targeting <=
0018 // 17733 Just for compile, ConPty doesn't work with Windows SDK < 17733
0019 #ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
0020 #define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE ProcThreadAttributeValue(22, FALSE, TRUE, FALSE)
0021 
0022 typedef VOID *HPCON;
0023 
0024 #define TOO_OLD_WINSDK 0
0025 #endif
0026 
0027 template<typename T>
0028 std::vector<T> vectorFromString(const std::basic_string<T> &str)
0029 {
0030     return std::vector<T>(str.begin(), str.end());
0031 }
0032 
0033 // ConPTY available only on Windows 10 releazed after 1903 (19H1) Windows
0034 // release
0035 class WindowsContext
0036 {
0037 public:
0038     typedef HRESULT (*CreatePseudoConsolePtr)(COORD size, // ConPty Dimensions
0039                                               HANDLE hInput, // ConPty Input
0040                                               HANDLE hOutput, // ConPty Output
0041                                               DWORD dwFlags, // ConPty Flags
0042                                               HPCON *phPC); // ConPty Reference
0043 
0044     typedef HRESULT (*ResizePseudoConsolePtr)(HPCON hPC, COORD size);
0045 
0046     typedef VOID (*ClosePseudoConsolePtr)(HPCON hPC);
0047 
0048     WindowsContext()
0049         : createPseudoConsole(nullptr)
0050         , resizePseudoConsole(nullptr)
0051         , closePseudoConsole(nullptr)
0052     {
0053     }
0054 
0055     bool init()
0056     {
0057         // already initialized
0058         if (createPseudoConsole)
0059             return true;
0060 
0061         // try to load symbols from library
0062         // if it fails -> we can't use ConPty API
0063         HANDLE kernel32Handle = LoadLibraryExW(L"kernel32.dll", 0, 0);
0064 
0065         if (kernel32Handle != nullptr) {
0066             createPseudoConsole = (CreatePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "CreatePseudoConsole");
0067             resizePseudoConsole = (ResizePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ResizePseudoConsole");
0068             closePseudoConsole = (ClosePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ClosePseudoConsole");
0069             if (createPseudoConsole == nullptr || resizePseudoConsole == nullptr || closePseudoConsole == nullptr) {
0070                 m_lastError = QStringLiteral("WindowsContext/ConPty error: %1").arg(QStringLiteral("Invalid on load API functions"));
0071                 return false;
0072             }
0073         } else {
0074             m_lastError = QStringLiteral("WindowsContext/ConPty error: %1").arg(QStringLiteral("Unable to load kernel32"));
0075             return false;
0076         }
0077 
0078         return true;
0079     }
0080 
0081     QString lastError()
0082     {
0083         return m_lastError;
0084     }
0085 
0086 public:
0087     // vars
0088     CreatePseudoConsolePtr createPseudoConsole;
0089     ResizePseudoConsolePtr resizePseudoConsole;
0090     ClosePseudoConsolePtr closePseudoConsole;
0091 
0092 private:
0093     QString m_lastError;
0094 };
0095 
0096 class PtyBuffer : public QIODevice
0097 {
0098     friend class ConPtyProcess;
0099     Q_OBJECT
0100 public:
0101     PtyBuffer()
0102     {
0103     }
0104     ~PtyBuffer()
0105     {
0106     }
0107 
0108     // just empty realization, we need only 'readyRead' signal of this class
0109     qint64 readData(char *data, qint64 maxlen) override
0110     {
0111         return 0;
0112     }
0113     qint64 writeData(const char *data, qint64 len) override
0114     {
0115         return 0;
0116     }
0117 
0118     bool isSequential()
0119     {
0120         return true;
0121     }
0122     qint64 bytesAvailable()
0123     {
0124         return m_readBuffer.size();
0125     }
0126     qint64 size()
0127     {
0128         return m_readBuffer.size();
0129     }
0130 
0131     void emitReadyRead()
0132     {
0133         // for Q_EMIT signal from PtyBuffer own thread
0134         QTimer::singleShot(1, this, [this]() {
0135             Q_EMIT readyRead();
0136         });
0137     }
0138 
0139 private:
0140     QByteArray m_readBuffer;
0141 };
0142 
0143 class ConPtyProcess : public IPtyProcess
0144 {
0145 public:
0146     ConPtyProcess();
0147     ~ConPtyProcess();
0148 
0149     bool
0150     startProcess(const QString &shellPath, const QStringList &arguments, const QString &workingDirectory, QStringList environment, qint16 cols, qint16 rows);
0151     bool resize(qint16 cols, qint16 rows);
0152     bool kill();
0153     PtyType type();
0154     QString dumpDebugInfo();
0155     virtual QIODevice *notifier();
0156     virtual QByteArray readAll();
0157     virtual qint64 write(const char *data, int size);
0158     bool isAvailable();
0159     void moveToThread(QThread *targetThread);
0160     virtual int processList() const;
0161 
0162 private:
0163     HRESULT createPseudoConsoleAndPipes(HPCON *phPC, HANDLE *phPipeIn, HANDLE *phPipeOut, qint16 cols, qint16 rows);
0164     HRESULT
0165     initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC);
0166 
0167 private:
0168     WindowsContext m_winContext;
0169     HPCON m_ptyHandler;
0170     HANDLE m_hPipeIn, m_hPipeOut;
0171 
0172     QThread *m_readThread;
0173     QMutex m_bufferMutex;
0174     PtyBuffer m_buffer;
0175 
0176     bool m_aboutToDestruct{false};
0177     PROCESS_INFORMATION m_shellProcessInformation{};
0178     HANDLE m_shellCloseWaitHandle{INVALID_HANDLE_VALUE};
0179     STARTUPINFOEX m_shellStartupInfo{};
0180 };
0181 
0182 #endif // CONPTYPROCESS_H