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