From 23570ead276f3827e0d449d0d871b41b5f413692 Mon Sep 17 00:00:00 2001 From: KingToolbox Date: Mon, 24 Aug 2020 14:47:21 +0800 Subject: [PATCH] Add WinPty and ConPty --- src/Pty/ConPty.cpp | 261 ++++++++++++++++++++++++++++++++++++++++++ src/Pty/ConPty.h | 66 +++++++++++ src/Pty/Pty.cpp | 95 +++++++++++++++ src/Pty/Pty.h | 73 ++++++++++++ src/Pty/WinPty.cpp | 200 ++++++++++++++++++++++++++++++++ src/Pty/WinPty.h | 61 ++++++++++ src/Pty/ptyqt LICENSE | 21 ++++ src/README.md | 4 + 8 files changed, 781 insertions(+) create mode 100644 src/Pty/ConPty.cpp create mode 100644 src/Pty/ConPty.h create mode 100644 src/Pty/Pty.cpp create mode 100644 src/Pty/Pty.h create mode 100644 src/Pty/WinPty.cpp create mode 100644 src/Pty/WinPty.h create mode 100644 src/Pty/ptyqt LICENSE diff --git a/src/Pty/ConPty.cpp b/src/Pty/ConPty.cpp new file mode 100644 index 0000000..ccf37e7 --- /dev/null +++ b/src/Pty/ConPty.cpp @@ -0,0 +1,261 @@ + /* + * Copyright 2020, WindTerm. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ConPty.h" + +#include + +#ifdef Q_OS_WIN +#include +#endif + +#define CONPTY_MINIMAL_WINDOWS_VERSION 18309 + +class PipeThread : public QThread { +public: + PipeThread(ConPty *conpty, LPVOID pipe) + : QThread(conpty) + , m_conpty(conpty) + , m_pipe(pipe) + {} + + void run() final { + constexpr DWORD BUFF_SIZE = 512; + char szBuffer[BUFF_SIZE]; + + while (isInterruptionRequested() == false) { + if (isInterruptionRequested()) { + return; + } + DWORD bytesRead; + bool readSuccess = ReadFile(m_pipe, szBuffer, BUFF_SIZE, &bytesRead, NULL); + + if (readSuccess == false) { + m_conpty->setErrorCode(GetLastError()); + return; + } + + if (isInterruptionRequested()) { + return; + } + + if (readSuccess && bytesRead > 0) { + m_conpty->appendBuffer(QByteArray(szBuffer, bytesRead)); + } + } + } + +private: + ConPty *m_conpty; + LPVOID m_pipe; +}; + +ConPty::ConPty(QObject *parent /*= nullptr*/) + : m_inPipe(INVALID_HANDLE_VALUE) + , m_outPipe(INVALID_HANDLE_VALUE) + , m_pipeThread(nullptr) + , m_ptyHandler(INVALID_HANDLE_VALUE) +{} + +ConPty::~ConPty() { + stop(); +} + +void ConPty::appendBuffer(const QByteArray &buffer) { + if (buffer.isEmpty() == false) { + { + ThreadLocker locker(m_mutex); + m_buffer.append(buffer); + } + emit readyRead(); + } +} + +HRESULT ConPty::createPseudoConsoleAndPipes(HPCON *phPC, HANDLE *phPipeIn, HANDLE *phPipeOut, + qint16 rows, qint16 columns) { + HRESULT hr = E_UNEXPECTED; + HANDLE hPipePTYIn = INVALID_HANDLE_VALUE; + HANDLE hPipePTYOut = INVALID_HANDLE_VALUE; + + if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) && CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0)) { +#if COMPILE_CONPTY_ENABLED + hr = CreatePseudoConsole({columns, rows}, hPipePTYIn, hPipePTYOut, 0, phPC); +#endif + if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut); + if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn); + } + return hr; +} + +HRESULT ConPty::initStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC) { + HRESULT hr = E_UNEXPECTED; + + if (pStartupInfo) { + SIZE_T attrListSize; + + pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX); + + InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize); + + pStartupInfo->lpAttributeList = reinterpret_cast(malloc(attrListSize)); + + if (pStartupInfo->lpAttributeList + && InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize)) { + hr = UpdateProcThreadAttribute( + pStartupInfo->lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + hPC, + sizeof(HPCON), + NULL, + NULL + ) ? S_OK : HRESULT_FROM_WIN32(GetLastError()); + } else { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + } + return hr; +} + +bool ConPty::createProcess(QString command, const QString &arguments, + const QString &workingDirectory, const QStringList &environment, + qint16 rows, qint16 columns) { + if (isAvailable() == false) { + setErrorString(tr("Windows 10 version below 1809 is not supported.")); + return false; + } + stop(); + + HRESULT hr = createPseudoConsoleAndPipes(&m_ptyHandler, &m_inPipe, &m_outPipe, rows, columns); + + if (hr == S_OK) { + m_startupInfo = std::make_unique(); + m_processInformation = std::make_unique(); + + m_pipeThread = new PipeThread(this, m_inPipe); + m_pipeThread->start(); + + if (initStartupInfoAttachedToPseudoConsole(m_startupInfo.get(), m_ptyHandler) == S_OK) { + std::wstring env = environment.join(QChar('\0')).append(QChar('\0')).toStdWString(); + + if (arguments.isEmpty() == false) { + command.append(" ").append(arguments); + } + + LPWSTR szCommand = new wchar_t[command.size() + 1]; + int commandLength = command.toWCharArray(szCommand); + szCommand[commandLength] = '\0'; + + hr = CreateProcess( + NULL, + szCommand, + NULL, + NULL, + FALSE, + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, + LPWSTR(env.data()), + workingDirectory.isEmpty() ? NULL : workingDirectory.toStdWString().c_str(), + &m_startupInfo->StartupInfo, + m_processInformation.get() + ) ? S_OK : GetLastError(); + + delete szCommand; + szCommand = nullptr; + } + } + + if (hr == S_OK) { + m_rows = rows; + m_columns = columns; + + installWinProcessEventNotifier(m_processInformation->hProcess); + } else { + setErrorCode(GetLastError()); + } + return true; +} + +bool ConPty::isAvailable() { + qint32 buildNumber = QSysInfo::kernelVersion().split(".").last().toInt(); + return (buildNumber >= CONPTY_MINIMAL_WINDOWS_VERSION) ? true : false; +} + +QByteArray ConPty::readAll() { + ThreadLocker locker(m_mutex); + return std::move(m_buffer); +} + +bool ConPty::resizeWindow(qint16 rows, qint16 columns) { + bool success = true; + + if (rows != m_rows && columns != m_columns) { +#if COMPILE_CONPTY_ENABLED + HRESULT hr = (m_ptyHandler != INVALID_HANDLE_VALUE) + ? ResizePseudoConsole(m_ptyHandler, { columns, rows }) + : S_FALSE; + success = (hr == S_OK) ? true : false; +#endif + if (success) { + rows = m_rows; + columns = m_columns; + } + } + Q_ASSERT(success); + return success; +} + +void ConPty::stop() { + if (m_pipeThread) { + m_pipeThread->requestInterruption(); + } + + if (m_processInformation) { + uninstallWinProcessEventNotifier(m_processInformation->hProcess); + CloseHandle(m_processInformation->hThread); + CloseHandle(m_processInformation->hProcess); + } + + if (m_startupInfo) { + DeleteProcThreadAttributeList(m_startupInfo->lpAttributeList); + free(m_startupInfo->lpAttributeList); + } + + if (m_ptyHandler != INVALID_HANDLE_VALUE) { +#if COMPILE_CONPTY_ENABLED + ClosePseudoConsole(m_ptyHandler); +#endif + } + + if (m_inPipe != INVALID_HANDLE_VALUE) { + CloseHandle(m_inPipe); + } + + if (m_outPipe != INVALID_HANDLE_VALUE) { + CloseHandle(m_outPipe); + } + + if (m_pipeThread) { + m_pipeThread->wait(1000); + m_pipeThread->deleteLater(); + } +} + +qint64 ConPty::write(const QByteArray &text) { + DWORD bytesWritten; + + WriteFile(m_outPipe, text.data(), text.size(), &bytesWritten, NULL); + return bytesWritten; +} diff --git a/src/Pty/ConPty.h b/src/Pty/ConPty.h new file mode 100644 index 0000000..84e25b2 --- /dev/null +++ b/src/Pty/ConPty.h @@ -0,0 +1,66 @@ + /* + * Copyright 2020, WindTerm. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CONPTY_H +#define CONPTY_H + +#pragma once + +#include "Pty.h" + +#ifdef Q_OS_WIN + #include +#endif // Q_OS_WIN + +class PipeThread; + +class ConPty + : public Pty +{ + Q_OBJECT + +public: + ConPty(QObject *parent = nullptr); + virtual ~ConPty(); + + void appendBuffer(const QByteArray &buffer); + bool createProcess(QString command, const QString &arguments, + const QString &workingDirectory, const QStringList &environment, + qint16 rows, qint16 columns) final; + static bool isAvailable(); + QByteArray readAll() final; + bool resizeWindow(qint16 rows, qint16 columns) final; + qint64 write(const QByteArray &text) final; + +private: + HRESULT createPseudoConsoleAndPipes(HPCON *phPC, HANDLE *phPipeIn, HANDLE *phPipeOut, qint16 rows, qint16 columns); + HRESULT initStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC); + void stop(); + +private: + Q_DISABLE_COPY(ConPty) + + QByteArray m_buffer; + PipeThread *m_pipeThread; + + HANDLE m_inPipe; + HANDLE m_outPipe; + HPCON m_ptyHandler; + std::unique_ptr m_processInformation; + std::unique_ptr m_startupInfo; +}; + +#endif // CONPTY_H \ No newline at end of file diff --git a/src/Pty/Pty.cpp b/src/Pty/Pty.cpp new file mode 100644 index 0000000..83fdac7 --- /dev/null +++ b/src/Pty/Pty.cpp @@ -0,0 +1,95 @@ + /* + * Copyright 2020, WindTerm. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Pty.h" + +#ifdef Q_OS_WIN + #include + #include +#endif // Q_OS_WIN + +Pty::Pty() + : m_columns(-1) + , m_errorCode(0) + , m_rows(-1) +#ifdef Q_OS_WIN + , m_winProcessEventNotifier(nullptr) +#endif +{} + +QString Pty::errorString() { + ThreadLocker locker(m_mutex); + return std::move(m_errorString); +} + +#ifdef Q_OS_WIN +void Pty::installWinProcessEventNotifier(void *handle) { + if (m_winProcessEventNotifier == nullptr) { + m_winProcessEventNotifier = new QWinEventNotifier(handle, this); + + connect(m_winProcessEventNotifier, &QWinEventNotifier::activated, this, [this](HANDLE handle) { + if (handle) { + DWORD exitCode; + + if (GetExitCodeProcess(handle, &exitCode)) { + setErrorString(QString("Process exited with code %1").arg( + QString::number(exitCode, (exitCode >= 0xFF) ? 16 : 10).prepend((exitCode >= 0xFF) ? "0x" : "") + )); + } + m_winProcessEventNotifier->setEnabled(false); + } + }); + } + + if (m_winProcessEventNotifier->handle() != handle) { + m_winProcessEventNotifier->setHandle(handle); + m_winProcessEventNotifier->setEnabled(true); + } +} +#endif + +void Pty::setErrorCode(int errorCode) { + constexpr int bufferLength = 512; + wchar_t buffer[bufferLength]; + + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode, + LANG_NEUTRAL, buffer, bufferLength, NULL); + + QString lastError = QString::fromWCharArray(buffer); + setErrorString(lastError); + + m_errorCode = errorCode; +} + +void Pty::setErrorString(const QString &errorString) { + if (errorString.isEmpty() == false) { + { + ThreadLocker locker(m_mutex); + m_errorString = errorString; + } + emit errorOccurred(); + } +} + +#ifdef Q_OS_WIN +void Pty::uninstallWinProcessEventNotifier(void *handle) { + if (m_winProcessEventNotifier != nullptr + && m_winProcessEventNotifier->handle() == handle) { + m_winProcessEventNotifier->deleteLater(); + m_winProcessEventNotifier = nullptr; + } +} +#endif diff --git a/src/Pty/Pty.h b/src/Pty/Pty.h new file mode 100644 index 0000000..4de8d27 --- /dev/null +++ b/src/Pty/Pty.h @@ -0,0 +1,73 @@ + /* + * Copyright 2020, WindTerm. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PTY_H +#define PTY_H + +#include + +#include "Public/Spin.h" + +class QWinEventNotifier; + +class Pty + : public QObject +{ + Q_OBJECT + +public: + Pty(); + virtual ~Pty() = default; + + virtual bool createProcess(QString command, const QString &arguments, + const QString &workingDirectory, const QStringList &environment, + qint16 rows, qint16 columns) = 0; + int errorCode() const { return m_errorCode; } + QString errorString(); + virtual QByteArray readAll() = 0; + virtual bool resizeWindow(qint16 rows, qint16 columns) = 0; + void setErrorCode(int errorCode); + void setErrorString(const QString &errorString); + virtual qint64 write(const QByteArray &text) = 0; + +protected: +#ifdef Q_OS_WIN + void installWinProcessEventNotifier(void *handle); + void uninstallWinProcessEventNotifier(void *handle); +#endif + +Q_SIGNALS: + void errorOccurred(); + void readyRead(); + +protected: + SpinMutex m_mutex; + + qint16 m_columns; + qint16 m_rows; + +private: + Q_DISABLE_COPY(Pty) + + int m_errorCode; + QString m_errorString; + +#ifdef Q_OS_WIN + QWinEventNotifier *m_winProcessEventNotifier; +#endif +}; + +#endif // PTY_H diff --git a/src/Pty/WinPty.cpp b/src/Pty/WinPty.cpp new file mode 100644 index 0000000..0a0d9bb --- /dev/null +++ b/src/Pty/WinPty.cpp @@ -0,0 +1,200 @@ + /* + * Copyright 2020, WindTerm. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "WinPty.h" + +#include +#include +#include + +#include + +const char *WINPTY_AGENT_NAME = "winpty-agent.exe"; +const char *WINPTY_DLL_NAME = "winpty.dll"; + +QString castErrorToString(winpty_error_ptr_t error_ptr) { + return QString::fromStdWString(winpty_error_msg(error_ptr)); +} + +WinPty::WinPty(QObject *parent /*= nullptr*/) + : m_ptyHandler(nullptr) + , m_innerHandle(INVALID_HANDLE_VALUE) + , m_outSocket(nullptr) + , m_inSocket(nullptr) +{} + +WinPty::~WinPty() { + stop(); +} + +bool WinPty::createProcess(QString command, const QString &arguments, + const QString &workingDirectory, const QStringList &environment, + qint16 rows, qint16 columns) { + bool success = false; + winpty_error_ptr_t errorPtr = nullptr; + QString errorString; + + do { + stop(); + + if (isAvailable() == false) { + errorString = tr("Winpty-agent.exe or winpty.dll not found!."); + break; + } + QString commandWithArguments = command; + + if (arguments.isEmpty() == false) { + commandWithArguments.append(" ").append(arguments); + } + std::wstring env = environment.join(QChar('\0')).append(QChar('\0')).toStdWString(); + winpty_config_t* startConfig = winpty_config_new(0, &errorPtr); + + if (startConfig == nullptr) { + errorString = QString("WinPty Error: create start config -> %1").arg(castErrorToString(errorPtr)); + break; + } + winpty_config_set_initial_size(startConfig, columns, rows); + winpty_config_set_mouse_mode(startConfig, WINPTY_MOUSE_MODE_AUTO); + + m_ptyHandler = winpty_open(startConfig, &errorPtr); + winpty_config_free(startConfig); + + if (m_ptyHandler == nullptr) { + errorString = QString("WinPty Error: start agent -> %1").arg(castErrorToString(errorPtr)); + break; + } + + QString m_conInName = QString::fromWCharArray(winpty_conin_name(m_ptyHandler)); + QString m_conOutName = QString::fromWCharArray(winpty_conout_name(m_ptyHandler)); + m_outSocket = std::make_unique(); + m_inSocket = std::make_unique(); + + m_outSocket->connectToServer(m_conInName, QIODevice::WriteOnly); + m_outSocket->waitForConnected(); + + m_inSocket->connectToServer(m_conOutName, QIODevice::ReadOnly); + m_inSocket->waitForConnected(); + + if (m_outSocket->state() != QLocalSocket::ConnectedState + && m_inSocket->state() != QLocalSocket::ConnectedState) { + errorString = QString("WinPty Error: Unable to connect local sockets -> %1 / %2") + .arg(m_outSocket->errorString()) + .arg(m_inSocket->errorString()); + + m_inSocket.reset(nullptr); + m_outSocket.reset(nullptr); + break; + } + + connect(m_inSocket.get(), &QLocalSocket::readyRead, this, [this]() { + emit readyRead(); + }); + + winpty_spawn_config_t* spawnConfig = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, + command.toStdWString().c_str(), + commandWithArguments.toStdWString().c_str(), + workingDirectory.isEmpty() ? NULL : workingDirectory.toStdWString().c_str(), + env.c_str(), + &errorPtr + ); + + if (spawnConfig == nullptr) { + errorString = QString("WinPty Error: create spawn config -> %1").arg(castErrorToString(errorPtr)); + break; + } + + BOOL spawnSuccess = winpty_spawn(m_ptyHandler, spawnConfig, &m_innerHandle, nullptr, nullptr, &errorPtr); + winpty_spawn_config_free(spawnConfig); + + if (spawnSuccess == FALSE) { + errorString = QString("WinPty Error: start terminal process -> %1").arg(castErrorToString(errorPtr)); + break; + } + success = true; + } while (0); + + if (errorString.isEmpty() == false) { + Q_ASSERT(success == false); + + winpty_error_free(errorPtr); + setErrorString(errorString); + } + + if (success) { + m_columns = columns; + m_rows = rows; + + installWinProcessEventNotifier(m_innerHandle); + } + return success; +} + +QByteArray WinPty::readAll() { + QByteArray buffer; + + if (m_inSocket) { + buffer = m_inSocket->readAll(); + Q_ASSERT(buffer.isEmpty() == false); + } + return buffer; +} + +bool WinPty::resizeWindow(qint16 rows, qint16 columns) { + bool success = true; + + if (rows != m_rows && columns != m_columns) { + success = m_ptyHandler ? winpty_set_size(m_ptyHandler, columns, rows, nullptr) : false; + + if (success) { + m_rows = rows; + m_columns = columns; + } + } + Q_ASSERT(success); + return success; +} + +void WinPty::stop() { + if (m_ptyHandler != nullptr) { + winpty_free(m_ptyHandler); + m_ptyHandler = nullptr; + } + + if (m_innerHandle != INVALID_HANDLE_VALUE) { + uninstallWinProcessEventNotifier(m_innerHandle); + CloseHandle(m_innerHandle); + m_innerHandle = INVALID_HANDLE_VALUE; + } + + m_outSocket.reset(nullptr); + m_inSocket.reset(nullptr); +} + +qint64 WinPty::write(const QByteArray &text) { + qint64 bytesWritten = -1; + + if (m_outSocket) { + bytesWritten = m_outSocket->write(text); + Q_ASSERT(bytesWritten != -1); + } + return bytesWritten; +} + +bool WinPty::isAvailable() { + return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME) + && QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_DLL_NAME); +} \ No newline at end of file diff --git a/src/Pty/WinPty.h b/src/Pty/WinPty.h new file mode 100644 index 0000000..419239b --- /dev/null +++ b/src/Pty/WinPty.h @@ -0,0 +1,61 @@ + /* + * Copyright 2020, WindTerm. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPTY_H +#define WINPTY_H + +#pragma once + +#include "Pty.h" + +#ifdef Q_OS_WIN + #include +#endif // Q_OS_WIN + +#include "winpty_api.h" + +class QLocalSocket; + +class WinPty + : public Pty +{ + Q_OBJECT + +public: + WinPty(QObject *parent = nullptr); + virtual ~WinPty(); + + bool createProcess(QString command, const QString &arguments, + const QString &workingDirectory, const QStringList &environment, + qint16 rows, qint16 columns) final; + static bool isAvailable(); + QByteArray readAll() final; + bool resizeWindow(qint16 rows, qint16 columns) final; + qint64 write(const QByteArray &text) final; + +private: + void stop(); + +private: + Q_DISABLE_COPY(WinPty) + + winpty_t *m_ptyHandler; + HANDLE m_innerHandle; + std::unique_ptr m_inSocket; + std::unique_ptr m_outSocket; +}; + +#endif // WINPTY_H \ No newline at end of file diff --git a/src/Pty/ptyqt LICENSE b/src/Pty/ptyqt LICENSE new file mode 100644 index 0000000..52e3fbb --- /dev/null +++ b/src/Pty/ptyqt LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Vitaly Petrov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/README.md b/src/README.md index 9cba008..fe87f93 100644 --- a/src/README.md +++ b/src/README.md @@ -16,6 +16,10 @@ A very safe encryption class using the PBKDF2-algorithm as defined in RFC 8018. An improved version based on Onigmo 5.13.5. In particular, **the addition of iterator makes it possible to match gap buffer or nonadjacent memory blocks.** Please refer to the sample files for how to use. +## Pty + +An improved version based on ptyqt[https://github.com/kafeg/ptyqt]. **Almost all the code was rewritten to make the pty more robust and stable.** + ## ScopeGuard.h A class of which the sole purpose is to run the function f in its destructor. This is useful for guaranteeing your cleanup code is executed.