aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/3rdparty/libptyqt
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/3rdparty/libptyqt')
-rw-r--r--src/libs/3rdparty/libptyqt/.clang-format1
-rw-r--r--src/libs/3rdparty/libptyqt/CMakeLists.txt28
-rw-r--r--src/libs/3rdparty/libptyqt/LICENSE21
-rw-r--r--src/libs/3rdparty/libptyqt/LICENSE-CONPTY21
-rw-r--r--src/libs/3rdparty/libptyqt/conptyprocess.cpp1148
-rw-r--r--src/libs/3rdparty/libptyqt/conptyprocess.h90
-rw-r--r--src/libs/3rdparty/libptyqt/iptyprocess.h62
-rw-r--r--src/libs/3rdparty/libptyqt/ptyqt.cpp56
-rw-r--r--src/libs/3rdparty/libptyqt/ptyqt.h14
-rw-r--r--src/libs/3rdparty/libptyqt/ptyqt.qbs40
-rw-r--r--src/libs/3rdparty/libptyqt/unixptyprocess.cpp362
-rw-r--r--src/libs/3rdparty/libptyqt/unixptyprocess.h66
-rw-r--r--src/libs/3rdparty/libptyqt/winptyprocess.cpp278
-rw-r--r--src/libs/3rdparty/libptyqt/winptyprocess.h43
14 files changed, 2230 insertions, 0 deletions
diff --git a/src/libs/3rdparty/libptyqt/.clang-format b/src/libs/3rdparty/libptyqt/.clang-format
new file mode 100644
index 0000000000..b861ff7a95
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/.clang-format
@@ -0,0 +1 @@
+{ "DisableFormat" : true } \ No newline at end of file
diff --git a/src/libs/3rdparty/libptyqt/CMakeLists.txt b/src/libs/3rdparty/libptyqt/CMakeLists.txt
new file mode 100644
index 0000000000..c6e8b74573
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(SOURCES
+ iptyprocess.h
+ ptyqt.cpp ptyqt.h
+)
+
+if (WIN32)
+ list(APPEND SOURCES
+ winptyprocess.cpp winptyprocess.h
+ conptyprocess.cpp conptyprocess.h
+ )
+else()
+ list(APPEND SOURCES unixptyprocess.cpp unixptyprocess.h)
+endif()
+
+add_library(ptyqt STATIC ${SOURCES})
+target_link_libraries(ptyqt PUBLIC Qt::Core)
+
+if (WIN32)
+ target_link_libraries(ptyqt PRIVATE winpty Qt::Network)
+ #target_compile_definitions(ptyqt PRIVATE PTYQT_DEBUG)
+endif()
+
+set_target_properties(ptyqt
+ PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}
+ QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON
+ POSITION_INDEPENDENT_CODE ON
+)
diff --git a/src/libs/3rdparty/libptyqt/LICENSE b/src/libs/3rdparty/libptyqt/LICENSE
new file mode 100644
index 0000000000..73996c7c90
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Vitaly Petrov, v31337@gmail.com
+
+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. \ No newline at end of file
diff --git a/src/libs/3rdparty/libptyqt/LICENSE-CONPTY b/src/libs/3rdparty/libptyqt/LICENSE-CONPTY
new file mode 100644
index 0000000000..8cb179cdb6
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/LICENSE-CONPTY
@@ -0,0 +1,21 @@
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+MIT License
+
+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/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp
new file mode 100644
index 0000000000..94bb53b583
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp
@@ -0,0 +1,1148 @@
+#include "conptyprocess.h"
+#include <QFile>
+#include <QFileInfo>
+#include <QThread>
+#include <sstream>
+#include <QTimer>
+#include <QMutexLocker>
+#include <QCoreApplication>
+#include <QWinEventNotifier>
+
+#include <qt_windows.h>
+
+#ifdef QTCREATOR_PCH_H
+#ifndef IN
+#define IN
+#endif
+#ifndef OUT
+#define OUT
+#endif
+#endif
+
+#include <winternl.h>
+
+#define READ_INTERVAL_MSEC 500
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020
+
+////////////////////////////////////////////////////////////////////////////////////////
+/// Ported from wil/result_macros.h
+
+#define FAILED_NTSTATUS(status) (((NTSTATUS)(status)) < 0)
+#define SUCCEEDED_NTSTATUS(status) (((NTSTATUS)(status)) >= 0)
+
+#define RETURN_IF_NTSTATUS_FAILED(call) if(FAILED_NTSTATUS(call)) return E_FAIL;
+#define RETURN_IF_WIN32_BOOL_FALSE(call) if((call) == FALSE) return E_FAIL;
+#define RETURN_IF_NULL_ALLOC(call) if((call) == nullptr) return E_OUTOFMEMORY;
+#define RETURN_IF_FAILED(call) {HRESULT hr = (call); if(hr != S_OK)return hr; }
+
+//! Set zero or more bitflags specified by `flags` in the variable `var`.
+#define WI_SetAllFlags(var, flags) ((var) |= (flags))
+//! Set a single compile-time constant `flag` in the variable `var`.
+#define WI_SetFlag(var, flag) WI_SetAllFlags(var,flag)
+
+////////////////////////////////////////////////////////////////////////////////////////
+/// Ported from wil
+class unique_hmodule : public std::unique_ptr<std::remove_pointer_t<HMODULE>, decltype(&FreeLibrary)>
+{
+public:
+ unique_hmodule() : std::unique_ptr<std::remove_pointer_t<HMODULE>, decltype(&FreeLibrary)>(nullptr, FreeLibrary) {}
+ unique_hmodule(HMODULE module) : std::unique_ptr<std::remove_pointer_t<HMODULE>, decltype(&FreeLibrary)>(module, FreeLibrary) {}
+};
+
+class unique_handle : public std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>
+{
+public:
+ unique_handle() : std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>(nullptr, CloseHandle) {}
+ unique_handle(HANDLE module) : std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>(module, CloseHandle) {}
+
+
+ class AddressOf {
+ public:
+ AddressOf(unique_handle &h) : m_h(h) {}
+ ~AddressOf() {
+ m_h.reset(m_dest);
+ }
+
+ operator PHANDLE() {return &m_dest;}
+
+ HANDLE m_dest{INVALID_HANDLE_VALUE};
+ unique_handle &m_h;
+ };
+
+ AddressOf addressof() {
+ return AddressOf(*this);
+ }
+};
+
+class unique_process_information : public PROCESS_INFORMATION
+{
+public:
+ unique_process_information() {
+ hProcess = 0;
+ hThread = 0;
+ dwProcessId = 0;
+ dwThreadId = 0;
+ }
+ ~unique_process_information() {
+ if (hProcess)
+ {
+ CloseHandle(hProcess);
+ }
+
+ if (hThread)
+ {
+ CloseHandle(hThread);
+ }
+ }
+
+ PROCESS_INFORMATION* addressof() {
+ return this;
+ }
+};
+
+template <typename TLambda>
+class on_scope_exit {
+public:
+ TLambda m_func;
+ bool m_call{true};
+ on_scope_exit(TLambda &&func) : m_func(std::move(func)) {}
+ ~on_scope_exit() {if(m_call)m_func();}
+
+ void release() {m_call = false;}
+};
+
+template <typename TLambda>
+[[nodiscard]] inline auto scope_exit(TLambda&& lambda) noexcept
+{
+ return on_scope_exit<TLambda>(std::forward<TLambda>(lambda));
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+class WinNTControl
+{
+public:
+ [[nodiscard]] static NTSTATUS NtOpenFile(_Out_ PHANDLE FileHandle,
+ _In_ ACCESS_MASK DesiredAccess,
+ _In_ POBJECT_ATTRIBUTES ObjectAttributes,
+ _Out_ PIO_STATUS_BLOCK IoStatusBlock,
+ _In_ ULONG ShareAccess,
+ _In_ ULONG OpenOptions);
+
+private:
+ WinNTControl();
+
+ WinNTControl(WinNTControl const&) = delete;
+ void operator=(WinNTControl const&) = delete;
+
+ static WinNTControl& GetInstance();
+
+ unique_hmodule const _NtDllDll;
+
+ typedef NTSTATUS(NTAPI* PfnNtOpenFile)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK, ULONG, ULONG);
+ PfnNtOpenFile const _NtOpenFile;
+};
+
+WinNTControl::WinNTControl() :
+ // NOTE: Use LoadLibraryExW with LOAD_LIBRARY_SEARCH_SYSTEM32 flag below to avoid unneeded directory traversal.
+ // This has triggered CPG boot IO warnings in the past.
+ _NtDllDll(/*THROW_LAST_ERROR_IF_NULL*/(LoadLibraryExW(L"ntdll.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))),
+ _NtOpenFile(reinterpret_cast<PfnNtOpenFile>(/*THROW_LAST_ERROR_IF_NULL*/(GetProcAddress(_NtDllDll.get(), "NtOpenFile"))))
+{
+}
+
+// Routine Description:
+// - Provides the singleton pattern for WinNT control. Stores the single instance and returns it.
+// Arguments:
+// - <none>
+// Return Value:
+// - Reference to the single instance of NTDLL.dll wrapped methods.
+WinNTControl& WinNTControl::GetInstance()
+{
+ static WinNTControl Instance;
+ return Instance;
+}
+
+// Routine Description:
+// - Provides access to the NtOpenFile method documented at:
+// https://msdn.microsoft.com/en-us/library/bb432381(v=vs.85).aspx
+// Arguments:
+// - See definitions at MSDN
+// Return Value:
+// - See definitions at MSDN
+[[nodiscard]] NTSTATUS WinNTControl::NtOpenFile(_Out_ PHANDLE FileHandle,
+ _In_ ACCESS_MASK DesiredAccess,
+ _In_ POBJECT_ATTRIBUTES ObjectAttributes,
+ _Out_ PIO_STATUS_BLOCK IoStatusBlock,
+ _In_ ULONG ShareAccess,
+ _In_ ULONG OpenOptions)
+{
+ return GetInstance()._NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions);
+}
+
+namespace DeviceHandle {
+
+/*++
+Routine Description:
+- This routine opens a handle to the console driver.
+
+Arguments:
+- Handle - Receives the handle.
+- DeviceName - Supplies the name to be used to open the console driver.
+- DesiredAccess - Supplies the desired access mask.
+- Parent - Optionally supplies the parent object.
+- Inheritable - Supplies a boolean indicating if the new handle is to be made inheritable.
+- OpenOptions - Supplies the open options to be passed to NtOpenFile. A common
+ option for clients is FILE_SYNCHRONOUS_IO_NONALERT, to make the handle
+ synchronous.
+
+Return Value:
+- NTSTATUS indicating if the handle was successfully created.
+--*/
+[[nodiscard]] NTSTATUS
+_CreateHandle(
+ _Out_ PHANDLE Handle,
+ _In_ PCWSTR DeviceName,
+ _In_ ACCESS_MASK DesiredAccess,
+ _In_opt_ HANDLE Parent,
+ _In_ BOOLEAN Inheritable,
+ _In_ ULONG OpenOptions)
+
+{
+ ULONG Flags = OBJ_CASE_INSENSITIVE;
+
+ if (Inheritable)
+ {
+ WI_SetFlag(Flags, OBJ_INHERIT);
+ }
+
+ UNICODE_STRING Name;
+#pragma warning(suppress : 26492) // const_cast is prohibited, but we can't avoid it for filling UNICODE_STRING.
+ Name.Buffer = const_cast<wchar_t*>(DeviceName);
+ //Name.Length = gsl::narrow_cast<USHORT>((wcslen(DeviceName) * sizeof(wchar_t)));
+ Name.Length = static_cast<unsigned short>((wcslen(DeviceName) * sizeof(wchar_t)));
+ Name.MaximumLength = Name.Length + sizeof(wchar_t);
+
+ OBJECT_ATTRIBUTES ObjectAttributes;
+#pragma warning(suppress : 26477) // The QOS part of this macro in the define is 0. Can't fix that.
+ InitializeObjectAttributes(&ObjectAttributes,
+ &Name,
+ Flags,
+ Parent,
+ nullptr);
+
+ IO_STATUS_BLOCK IoStatus;
+ return WinNTControl::NtOpenFile(Handle,
+ DesiredAccess,
+ &ObjectAttributes,
+ &IoStatus,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ OpenOptions);
+}
+
+/*++
+Routine Description:
+- This routine creates a handle to an input or output client of the given
+ server. No control io is sent to the server as this request must be coming
+ from the server itself.
+
+Arguments:
+- Handle - Receives a handle to the new client.
+- ServerHandle - Supplies a handle to the server to which to attach the
+ newly created client.
+- Name - Supplies the name of the client object.
+- Inheritable - Supplies a flag indicating if the handle must be inheritable.
+
+Return Value:
+- NTSTATUS indicating if the client was successfully created.
+--*/
+[[nodiscard]] NTSTATUS
+CreateClientHandle(
+ _Out_ PHANDLE Handle,
+ _In_ HANDLE ServerHandle,
+ _In_ PCWSTR Name,
+ _In_ BOOLEAN Inheritable)
+{
+ return _CreateHandle(Handle,
+ Name,
+ GENERIC_WRITE | GENERIC_READ | SYNCHRONIZE,
+ ServerHandle,
+ Inheritable,
+ FILE_SYNCHRONOUS_IO_NONALERT);
+}
+
+/*++
+Routine Description:
+- This routine creates a new server on the driver and returns a handle to it.
+
+Arguments:
+- Handle - Receives a handle to the new server.
+- Inheritable - Supplies a flag indicating if the handle must be inheritable.
+
+Return Value:
+- NTSTATUS indicating if the console was successfully created.
+--*/
+[[nodiscard]] NTSTATUS
+CreateServerHandle(
+ _Out_ PHANDLE Handle,
+ _In_ BOOLEAN Inheritable)
+{
+ return _CreateHandle(Handle,
+ L"\\Device\\ConDrv\\Server",
+ GENERIC_ALL,
+ nullptr,
+ Inheritable,
+ 0);
+}
+
+
+} // namespace DeviceHandle
+
+[[nodiscard]] static inline NTSTATUS CreateClientHandle(PHANDLE Handle, HANDLE ServerHandle, PCWSTR Name, BOOLEAN Inheritable)
+{
+ return DeviceHandle::CreateClientHandle(Handle, ServerHandle, Name, Inheritable);
+}
+
+[[nodiscard]] static inline NTSTATUS CreateServerHandle(PHANDLE Handle, BOOLEAN Inheritable)
+{
+ return DeviceHandle::CreateServerHandle(Handle, Inheritable);
+}
+
+typedef struct _PseudoConsole
+{
+ HANDLE hSignal;
+ HANDLE hPtyReference;
+ HANDLE hConPtyProcess;
+} PseudoConsole;
+
+// Signals
+// These are not defined publicly, but are used for controlling the conpty via
+// the signal pipe.
+#define PTY_SIGNAL_CLEAR_WINDOW (2u)
+#define PTY_SIGNAL_RESIZE_WINDOW (8u)
+
+// CreatePseudoConsole Flags
+// The other flag (PSEUDOCONSOLE_INHERIT_CURSOR) is actually defined in consoleapi.h in the OS repo
+#ifndef PSEUDOCONSOLE_INHERIT_CURSOR
+#define PSEUDOCONSOLE_INHERIT_CURSOR (0x1)
+#endif
+#define PSEUDOCONSOLE_RESIZE_QUIRK (0x2)
+#define PSEUDOCONSOLE_WIN32_INPUT_MODE (0x4)
+
+static QString qSystemDirectory()
+{
+ static const QString result = []() -> QString {
+ QVarLengthArray<wchar_t, MAX_PATH> fullPath = {};
+ UINT retLen = ::GetSystemDirectoryW(fullPath.data(), MAX_PATH);
+ if (retLen > MAX_PATH) {
+ fullPath.resize(retLen);
+ retLen = ::GetSystemDirectoryW(fullPath.data(), retLen);
+ }
+ // in some rare cases retLen might be 0
+ return QString::fromWCharArray(fullPath.constData(), int(retLen));
+ }();
+ return result;
+}
+
+// Function Description:
+// - Returns the path to conhost.exe as a process heap string.
+static QString _InboxConsoleHostPath()
+{
+ return QString("\\\\?\\%1\\conhost.exe").arg(qSystemDirectory());
+}
+
+// Function Description:
+// - Returns the path to either conhost.exe or the side-by-side OpenConsole, depending on whether this
+// module is building with Windows and OpenConsole could be found.
+// Return Value:
+// - A pointer to permanent storage containing the path to the console host.
+static const wchar_t* _ConsoleHostPath()
+{
+ // Use the magic of magic statics to only calculate this once.
+ static QString consoleHostPath = _InboxConsoleHostPath();
+ return reinterpret_cast<const wchar_t*>(consoleHostPath.utf16());
+}
+
+static bool _HandleIsValid(HANDLE h) noexcept
+{
+ return (h != INVALID_HANDLE_VALUE) && (h != nullptr);
+}
+
+
+
+HRESULT _CreatePseudoConsole(const HANDLE hToken,
+ const COORD size,
+ const HANDLE hInput,
+ const HANDLE hOutput,
+ const DWORD dwFlags,
+ _Inout_ PseudoConsole* pPty)
+{
+ if (pPty == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+ if (size.X == 0 || size.Y == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ unique_handle serverHandle;
+ RETURN_IF_NTSTATUS_FAILED(CreateServerHandle(serverHandle.addressof(), TRUE));
+
+ unique_handle signalPipeConhostSide;
+ unique_handle signalPipeOurSide;
+
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof(sa);
+ // Mark inheritable for signal handle when creating. It'll have the same value on the other side.
+ sa.bInheritHandle = FALSE;
+ sa.lpSecurityDescriptor = nullptr;
+
+ RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(signalPipeConhostSide.addressof(), signalPipeOurSide.addressof(), &sa, 0));
+ RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(signalPipeConhostSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
+
+ // GH4061: Ensure that the path to executable in the format is escaped so C:\Program.exe cannot collide with C:\Program Files
+ const wchar_t* pwszFormat = L"\"%s\" --headless %s%s%s--width %hu --height %hu --signal 0x%x --server 0x%x";
+ // This is plenty of space to hold the formatted string
+ wchar_t cmd[MAX_PATH]{};
+ const BOOL bInheritCursor = (dwFlags & PSEUDOCONSOLE_INHERIT_CURSOR) == PSEUDOCONSOLE_INHERIT_CURSOR;
+ const BOOL bResizeQuirk = (dwFlags & PSEUDOCONSOLE_RESIZE_QUIRK) == PSEUDOCONSOLE_RESIZE_QUIRK;
+ const BOOL bWin32InputMode = (dwFlags & PSEUDOCONSOLE_WIN32_INPUT_MODE) == PSEUDOCONSOLE_WIN32_INPUT_MODE;
+ swprintf_s(cmd,
+ MAX_PATH,
+ pwszFormat,
+ _ConsoleHostPath(),
+ bInheritCursor ? L"--inheritcursor " : L"",
+ bWin32InputMode ? L"--win32input " : L"",
+ bResizeQuirk ? L"--resizeQuirk " : L"",
+ size.X,
+ size.Y,
+ signalPipeConhostSide.get(),
+ serverHandle.get());
+
+ STARTUPINFOEXW siEx{ 0 };
+ siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
+ siEx.StartupInfo.hStdInput = hInput;
+ siEx.StartupInfo.hStdOutput = hOutput;
+ siEx.StartupInfo.hStdError = hOutput;
+ siEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+ // Only pass the handles we actually want the conhost to know about to it:
+ const size_t INHERITED_HANDLES_COUNT = 4;
+ HANDLE inheritedHandles[INHERITED_HANDLES_COUNT];
+ inheritedHandles[0] = serverHandle.get();
+ inheritedHandles[1] = hInput;
+ inheritedHandles[2] = hOutput;
+ inheritedHandles[3] = signalPipeConhostSide.get();
+
+ // Get the size of the attribute list. We need one attribute, the handle list.
+ SIZE_T listSize = 0;
+ InitializeProcThreadAttributeList(nullptr, 1, 0, &listSize);
+
+ // I have to use a HeapAlloc here because kernelbase can't link new[] or delete[]
+ PPROC_THREAD_ATTRIBUTE_LIST attrList = static_cast<PPROC_THREAD_ATTRIBUTE_LIST>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, listSize));
+ RETURN_IF_NULL_ALLOC(attrList);
+ auto attrListDelete = scope_exit([&]() noexcept {
+ HeapFree(GetProcessHeap(), 0, attrList);
+ });
+
+ siEx.lpAttributeList = attrList;
+ RETURN_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &listSize));
+ // Set cleanup data for ProcThreadAttributeList when successful.
+ auto cleanupProcThreadAttribute = scope_exit([&]() noexcept {
+ DeleteProcThreadAttributeList(siEx.lpAttributeList);
+ });
+ RETURN_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(siEx.lpAttributeList,
+ 0,
+ PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
+ inheritedHandles,
+ (INHERITED_HANDLES_COUNT * sizeof(HANDLE)),
+ nullptr,
+ nullptr));
+ unique_process_information pi;
+ { // wow64 disabled filesystem redirection scope
+ if (hToken == INVALID_HANDLE_VALUE || hToken == nullptr)
+ {
+ // Call create process
+ RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(_ConsoleHostPath(),
+ cmd,
+ nullptr,
+ nullptr,
+ TRUE,
+ EXTENDED_STARTUPINFO_PRESENT,
+ nullptr,
+ nullptr,
+ &siEx.StartupInfo,
+ pi.addressof()));
+ }
+ else
+ {
+ // Call create process
+ RETURN_IF_WIN32_BOOL_FALSE(CreateProcessAsUserW(hToken,
+ _ConsoleHostPath(),
+ cmd,
+ nullptr,
+ nullptr,
+ TRUE,
+ EXTENDED_STARTUPINFO_PRESENT,
+ nullptr,
+ nullptr,
+ &siEx.StartupInfo,
+ pi.addressof()));
+ }
+ }
+
+ // Move the process handle out of the PROCESS_INFORMATION into out Pseudoconsole
+ pPty->hConPtyProcess = pi.hProcess;
+ pi.hProcess = nullptr;
+
+ RETURN_IF_NTSTATUS_FAILED(CreateClientHandle(&pPty->hPtyReference,
+ serverHandle.get(),
+ L"\\Reference",
+ FALSE));
+
+ pPty->hSignal = signalPipeOurSide.release();
+
+ return S_OK;
+}
+
+// Function Description:
+// - Resizes the conpty
+// Arguments:
+// - hSignal: A signal pipe as returned by CreateConPty.
+// - size: The new dimensions of the conpty, in characters.
+// Return Value:
+// - S_OK if the call succeeded, else an appropriate HRESULT for failing to
+// write the resize message to the pty.
+HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size)
+{
+ if (pPty == nullptr || size.X < 0 || size.Y < 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ unsigned short signalPacket[3];
+ signalPacket[0] = PTY_SIGNAL_RESIZE_WINDOW;
+ signalPacket[1] = size.X;
+ signalPacket[2] = size.Y;
+
+ const BOOL fSuccess = WriteFile(pPty->hSignal, signalPacket, sizeof(signalPacket), nullptr, nullptr);
+ return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
+}
+
+// Function Description:
+// - Clears the conpty
+// Arguments:
+// - hSignal: A signal pipe as returned by CreateConPty.
+// Return Value:
+// - S_OK if the call succeeded, else an appropriate HRESULT for failing to
+// write the clear message to the pty.
+HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty)
+{
+ if (pPty == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+
+ unsigned short signalPacket[1];
+ signalPacket[0] = PTY_SIGNAL_CLEAR_WINDOW;
+
+ const BOOL fSuccess = WriteFile(pPty->hSignal, signalPacket, sizeof(signalPacket), nullptr, nullptr);
+ return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
+}
+
+// Function Description:
+// - This closes each of the members of a PseudoConsole. It does not free the
+// data associated with the PseudoConsole. This is helpful for testing,
+// where we might stack allocate a PseudoConsole (instead of getting a
+// HPCON via the API).
+// Arguments:
+// - pPty: A pointer to a PseudoConsole struct.
+// Return Value:
+// - <none>
+void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty)
+{
+ if (pPty != nullptr)
+ {
+ // See MSFT:19918626
+ // First break the signal pipe - this will trigger conhost to tear itself down
+ if (_HandleIsValid(pPty->hSignal))
+ {
+ CloseHandle(pPty->hSignal);
+ pPty->hSignal = nullptr;
+ }
+ // Then, wait on the conhost process before killing it.
+ // We do this to make sure the conhost finishes flushing any output it
+ // has yet to send before we hard kill it.
+ if (_HandleIsValid(pPty->hConPtyProcess))
+ {
+ // If the conhost is already dead, then that's fine. Presumably
+ // it's finished flushing it's output already.
+ DWORD dwExit = 0;
+ // If GetExitCodeProcess failed, it's likely conhost is already dead
+ // If so, skip waiting regardless of whatever error
+ // GetExitCodeProcess returned.
+ // We'll just go straight to killing conhost.
+ if (GetExitCodeProcess(pPty->hConPtyProcess, &dwExit) && dwExit == STILL_ACTIVE)
+ {
+ WaitForSingleObject(pPty->hConPtyProcess, INFINITE);
+ }
+
+ TerminateProcess(pPty->hConPtyProcess, 0);
+ CloseHandle(pPty->hConPtyProcess);
+ pPty->hConPtyProcess = nullptr;
+ }
+ // Then take care of the reference handle.
+ // TODO GH#1810: Closing the reference handle late leaves conhost thinking
+ // that we have an outstanding connected client.
+ if (_HandleIsValid(pPty->hPtyReference))
+ {
+ CloseHandle(pPty->hPtyReference);
+ pPty->hPtyReference = nullptr;
+ }
+ }
+}
+
+// Function Description:
+// - This closes each of the members of a PseudoConsole, and HeapFree's the
+// memory allocated to it. This should be used to cleanup any
+// PseudoConsoles that were created with CreatePseudoConsole.
+// Arguments:
+// - pPty: A pointer to a PseudoConsole struct.
+// Return Value:
+// - <none>
+VOID _ClosePseudoConsole(_In_ PseudoConsole* pPty)
+{
+ if (pPty != nullptr)
+ {
+ _ClosePseudoConsoleMembers(pPty);
+ HeapFree(GetProcessHeap(), 0, pPty);
+ }
+}
+
+extern "C" HRESULT ConptyCreatePseudoConsoleAsUser(_In_ HANDLE hToken,
+ _In_ COORD size,
+ _In_ HANDLE hInput,
+ _In_ HANDLE hOutput,
+ _In_ DWORD dwFlags,
+ _Out_ HPCON* phPC)
+{
+ if (phPC == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+ *phPC = nullptr;
+ if ((!_HandleIsValid(hInput)) && (!_HandleIsValid(hOutput)))
+ {
+ return E_INVALIDARG;
+ }
+
+ PseudoConsole* pPty = (PseudoConsole*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PseudoConsole));
+ RETURN_IF_NULL_ALLOC(pPty);
+ auto cleanupPty = scope_exit([&]() noexcept {
+ _ClosePseudoConsole(pPty);
+ });
+
+ unique_handle duplicatedInput;
+ unique_handle duplicatedOutput;
+ RETURN_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), hInput, GetCurrentProcess(), duplicatedInput.addressof(), 0, TRUE, DUPLICATE_SAME_ACCESS));
+ RETURN_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), hOutput, GetCurrentProcess(), duplicatedOutput.addressof(), 0, TRUE, DUPLICATE_SAME_ACCESS));
+
+ RETURN_IF_FAILED(_CreatePseudoConsole(hToken, size, duplicatedInput.get(), duplicatedOutput.get(), dwFlags, pPty));
+
+ *phPC = (HPCON)pPty;
+ cleanupPty.release();
+
+ return S_OK;
+}
+
+// These functions are defined in the console l1 apiset, which is generated from
+// the consoleapi.apx file in minkernel\apiset\libs\Console.
+
+// Function Description:
+// Creates a "Pseudo-console" (conpty) with dimensions (in characters)
+// provided by the `size` parameter. The caller should provide two handles:
+// - `hInput` is used for writing input to the pty, encoded as UTF-8 and VT sequences.
+// - `hOutput` is used for reading the output of the pty, encoded as UTF-8 and VT sequences.
+// Once the call completes, `phPty` will receive a token value to identify this
+// conpty object. This value should be used in conjunction with the other
+// Pseudoconsole API's.
+// `dwFlags` is used to specify optional behavior to the created pseudoconsole.
+// The flags can be combinations of the following values:
+// INHERIT_CURSOR: This will cause the created conpty to attempt to inherit the
+// cursor position of the parent terminal application. This can be useful
+// for applications like `ssh`, where ssh (currently running in a terminal)
+// might want to create a pseudoterminal session for an child application
+// and the child inherit the cursor position of ssh.
+// The created conpty will immediately emit a "Device Status Request" VT
+// sequence to hOutput, that should be replied to on hInput in the format
+// "\x1b[<r>;<c>R", where `<r>` is the row and `<c>` is the column of the
+// cursor position.
+// This requires a cooperating terminal application - if a caller does not
+// reply to this message, the conpty will not process any input until it
+// does. Most *nix terminals and the Windows Console (after Windows 10
+// Anniversary Update) will be able to handle such a message.
+
+extern "C" HRESULT WINAPI ConptyCreatePseudoConsole(_In_ COORD size,
+ _In_ HANDLE hInput,
+ _In_ HANDLE hOutput,
+ _In_ DWORD dwFlags,
+ _Out_ HPCON* phPC)
+{
+ return ConptyCreatePseudoConsoleAsUser(INVALID_HANDLE_VALUE, size, hInput, hOutput, dwFlags, phPC);
+}
+
+// Function Description:
+// Resizes the given conpty to the specified size, in characters.
+extern "C" HRESULT WINAPI ConptyResizePseudoConsole(_In_ HPCON hPC, _In_ COORD size)
+{
+ const PseudoConsole* const pPty = (PseudoConsole*)hPC;
+ HRESULT hr = pPty == nullptr ? E_INVALIDARG : S_OK;
+ if (SUCCEEDED(hr))
+ {
+ hr = _ResizePseudoConsole(pPty, size);
+ }
+ return hr;
+}
+
+// Function Description:
+// - Clear the contents of the conpty buffer, leaving the cursor row at the top
+// of the viewport.
+// - This is used exclusively by ConPTY to support GH#1193, GH#1882. This allows
+// a terminal to clear the contents of the ConPTY buffer, which is important
+// if the user would like to be able to clear the terminal-side buffer.
+extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC)
+{
+ const PseudoConsole* const pPty = (PseudoConsole*)hPC;
+ HRESULT hr = pPty == nullptr ? E_INVALIDARG : S_OK;
+ if (SUCCEEDED(hr))
+ {
+ hr = _ClearPseudoConsole(pPty);
+ }
+ return hr;
+}
+
+// Function Description:
+// Closes the conpty and all associated state.
+// Client applications attached to the conpty will also behave as though the
+// console window they were running in was closed.
+// This can fail if the conhost hosting the pseudoconsole failed to be
+// terminated, or if the pseudoconsole was already terminated.
+extern "C" VOID WINAPI ConptyClosePseudoConsole(_In_ HPCON hPC)
+{
+ PseudoConsole* const pPty = (PseudoConsole*)hPC;
+ if (pPty != nullptr)
+ {
+ _ClosePseudoConsole(pPty);
+ }
+}
+
+
+//ConPTY is available only on Windows 10 released after 1903 (19H1) Windows release
+class WindowsContext
+{
+private:
+ 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);
+
+ static WindowsContext &instance()
+ {
+ static WindowsContext ctx;
+ return ctx;
+ }
+
+ bool init()
+ {
+ createPseudoConsole = (CreatePseudoConsolePtr)ConptyCreatePseudoConsole;
+ resizePseudoConsole = (ResizePseudoConsolePtr)ConptyResizePseudoConsole;
+ closePseudoConsole = (ClosePseudoConsolePtr)ConptyClosePseudoConsole;
+
+ return true;
+ }
+
+ QString lastError()
+ {
+ return m_lastError;
+ }
+
+public:
+ //vars
+ CreatePseudoConsolePtr createPseudoConsole{nullptr};
+ ResizePseudoConsolePtr resizePseudoConsole{nullptr};
+ ClosePseudoConsolePtr closePseudoConsole{nullptr};
+
+private:
+ QString m_lastError;
+};
+
+static bool checkConHostHasResizeQuirkOption()
+{
+ static bool hasResizeQuirk = std::invoke([](){
+ QFile f(_InboxConsoleHostPath());
+ if (!f.open(QIODevice::ReadOnly)) {
+ qWarning() << "couldn't open conhost.exe, assuming no resizeQuirk.";
+ return false;
+ }
+ // Conhost.exe should be around 1 MB
+ if (f.size() > 5 * 1024 * 1024) {
+ qWarning() << "conhost.exe is > 5MB, assuming no resizeQuirk.";
+ return false;
+ }
+ QByteArray content = f.readAll();
+ QString searchString("--resizeQuirk");
+ QByteArrayView v((const char*)searchString.data(), searchString.length()*2);
+ bool result = content.contains(v);
+ if (!result)
+ qDebug() << "No resizeQuirk option found in conhost.";
+ return result;
+ });
+
+ return hasResizeQuirk;
+}
+
+HRESULT ConPtyProcess::createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut, qint16 cols, qint16 rows)
+{
+ HRESULT hr{ E_UNEXPECTED };
+ HANDLE hPipePTYIn{ INVALID_HANDLE_VALUE };
+ HANDLE hPipePTYOut{ INVALID_HANDLE_VALUE };
+
+ // Create the pipes to which the ConPTY will connect
+ if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) &&
+ CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0))
+ {
+ // Create the Pseudo Console of the required size, attached to the PTY-end of the pipes
+ hr = WindowsContext::instance().createPseudoConsole({cols, rows}, hPipePTYIn, hPipePTYOut, checkConHostHasResizeQuirkOption() ? PSEUDOCONSOLE_RESIZE_QUIRK : 0, phPC);
+
+ // Note: We can close the handles to the PTY-end of the pipes here
+ // because the handles are dup'ed into the ConHost and will be released
+ // when the ConPTY is destroyed.
+ if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut);
+ if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn);
+ }
+
+ return hr;
+}
+
+// Initializes the specified startup info struct with the required properties and
+// updates its thread attribute list with the specified ConPTY handle
+HRESULT ConPtyProcess::initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC)
+{
+ HRESULT hr{ E_UNEXPECTED };
+
+ if (pStartupInfo)
+ {
+ SIZE_T attrListSize{};
+
+ pStartupInfo->StartupInfo.hStdInput = m_hPipeIn;
+ pStartupInfo->StartupInfo.hStdError = m_hPipeOut;
+ pStartupInfo->StartupInfo.hStdOutput = m_hPipeOut;
+ pStartupInfo->StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+ pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX);
+
+ // Get the size of the thread attribute list.
+ InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
+
+ // Allocate a thread attribute list of the correct size
+ pStartupInfo->lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
+ HeapAlloc(GetProcessHeap(), 0, attrListSize));
+
+ // Initialize thread attribute list
+ if (pStartupInfo->lpAttributeList
+ && InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize))
+ {
+ // Set Pseudo Console attribute
+ 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;
+}
+
+Q_DECLARE_METATYPE(HANDLE)
+
+ConPtyProcess::ConPtyProcess()
+ : IPtyProcess()
+ , m_ptyHandler { INVALID_HANDLE_VALUE }
+ , m_hPipeIn { INVALID_HANDLE_VALUE }
+ , m_hPipeOut { INVALID_HANDLE_VALUE }
+ , m_readThread(nullptr)
+{
+ qRegisterMetaType<HANDLE>("HANDLE");
+}
+
+ConPtyProcess::~ConPtyProcess()
+{
+ kill();
+}
+
+bool ConPtyProcess::startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows)
+{
+ if (!isAvailable()) {
+ m_lastError = WindowsContext::instance().lastError();
+ return false;
+ }
+
+ //already running
+ if (m_ptyHandler != INVALID_HANDLE_VALUE)
+ return false;
+
+ QFileInfo fi(executable);
+ if (fi.isRelative() || !QFile::exists(executable)) {
+ //todo add auto-find executable in PATH env var
+ m_lastError = QString("ConPty Error: shell file path '%1' must be absolute").arg(executable);
+ return false;
+ }
+
+ m_shellPath = executable;
+ m_shellPath.replace('/', '\\');
+ m_size = QPair<qint16, qint16>(cols, rows);
+
+ //env
+ const QString env = environment.join(QChar(QChar::Null)) + QChar(QChar::Null);
+ LPVOID envPtr = env.isEmpty() ? nullptr : (LPVOID) env.utf16();
+
+ LPCWSTR workingDirPtr = workingDir.isEmpty() ? nullptr : (LPCWSTR) workingDir.utf16();
+
+ QStringList exeAndArgs = arguments;
+ exeAndArgs.prepend(m_shellPath);
+ std::wstring cmdArg{(LPCWSTR) (exeAndArgs.join(QLatin1String(" ")).utf16())};
+
+ HRESULT hr{E_UNEXPECTED};
+
+ // Create the Pseudo Console and pipes to it
+ hr = createPseudoConsoleAndPipes(&m_ptyHandler, &m_hPipeIn, &m_hPipeOut, cols, rows);
+
+ if (S_OK != hr) {
+ m_lastError = QString("ConPty Error: CreatePseudoConsoleAndPipes fail");
+ return false;
+ }
+
+ // Initialize the necessary startup info struct
+ if (S_OK != initializeStartupInfoAttachedToPseudoConsole(&m_shellStartupInfo, m_ptyHandler)) {
+ m_lastError = QString("ConPty Error: InitializeStartupInfoAttachedToPseudoConsole fail");
+ return false;
+ }
+
+ hr = CreateProcessW(nullptr, // No module name - use Command Line
+ cmdArg.data(), // Command Line
+ nullptr, // Process handle not inheritable
+ nullptr, // Thread handle not inheritable
+ FALSE, // Inherit handles
+ EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // Creation flags
+ envPtr, // Environment block
+ workingDirPtr, // Use parent's starting directory
+ &m_shellStartupInfo.StartupInfo, // Pointer to STARTUPINFO
+ &m_shellProcessInformation) // Pointer to PROCESS_INFORMATION
+ ? S_OK
+ : GetLastError();
+
+ if (S_OK != hr) {
+ m_lastError = QString("ConPty Error: Cannot create process -> %1").arg(hr);
+ return false;
+ }
+
+ m_pid = m_shellProcessInformation.dwProcessId;
+
+ // Notify when the shell process has been terminated
+ m_shellCloseWaitNotifier = new QWinEventNotifier(m_shellProcessInformation.hProcess, notifier());
+ QObject::connect(m_shellCloseWaitNotifier,
+ &QWinEventNotifier::activated,
+ notifier(),
+ [this](HANDLE hEvent) {
+ DWORD exitCode = 0;
+ GetExitCodeProcess(hEvent, &exitCode);
+ m_exitCode = exitCode;
+ // Do not respawn if the object is about to be destructed
+ if (!m_aboutToDestruct)
+ emit notifier()->aboutToClose();
+ m_shellCloseWaitNotifier->setEnabled(false);
+ }, Qt::QueuedConnection);
+
+ //this code runned in separate thread
+ m_readThread = QThread::create([this]() {
+ //buffers
+ const DWORD BUFF_SIZE{1024};
+ char szBuffer[BUFF_SIZE]{};
+
+ forever {
+ DWORD dwBytesRead{};
+
+ // Read from the pipe
+ BOOL result = ReadFile(m_hPipeIn, szBuffer, BUFF_SIZE, &dwBytesRead, NULL);
+
+ const bool needMoreData = !result && GetLastError() == ERROR_MORE_DATA;
+ if (result || needMoreData) {
+ QMutexLocker locker(&m_bufferMutex);
+ m_buffer.m_readBuffer.append(szBuffer, dwBytesRead);
+ m_buffer.emitReadyRead();
+ }
+
+ const bool brokenPipe = !result && GetLastError() == ERROR_BROKEN_PIPE;
+ if (QThread::currentThread()->isInterruptionRequested() || brokenPipe)
+ break;
+ }
+
+ CancelIoEx(m_hPipeIn, nullptr);
+ });
+
+ //start read thread
+ m_readThread->start();
+
+ return true;
+}
+
+bool ConPtyProcess::resize(qint16 cols, qint16 rows)
+{
+ if (m_ptyHandler == nullptr)
+ {
+ return false;
+ }
+
+ bool res = SUCCEEDED(WindowsContext::instance().resizePseudoConsole(m_ptyHandler, {cols, rows}));
+
+ if (res)
+ {
+ m_size = QPair<qint16, qint16>(cols, rows);
+ }
+
+ return res;
+
+ return true;
+}
+
+bool ConPtyProcess::kill()
+{
+ bool exitCode = false;
+
+ if (m_ptyHandler != INVALID_HANDLE_VALUE) {
+ m_aboutToDestruct = true;
+
+ // Close ConPTY - this will terminate client process if running
+ WindowsContext::instance().closePseudoConsole(m_ptyHandler);
+
+ // Clean-up the pipes
+ if (INVALID_HANDLE_VALUE != m_hPipeOut)
+ CloseHandle(m_hPipeOut);
+ if (INVALID_HANDLE_VALUE != m_hPipeIn)
+ CloseHandle(m_hPipeIn);
+
+ if (m_readThread) {
+ m_readThread->requestInterruption();
+ if (!m_readThread->wait(1000))
+ m_readThread->terminate();
+ m_readThread->deleteLater();
+ m_readThread = nullptr;
+ }
+
+ delete m_shellCloseWaitNotifier;
+ m_shellCloseWaitNotifier = nullptr;
+
+ m_pid = 0;
+ m_ptyHandler = INVALID_HANDLE_VALUE;
+ m_hPipeIn = INVALID_HANDLE_VALUE;
+ m_hPipeOut = INVALID_HANDLE_VALUE;
+
+ CloseHandle(m_shellProcessInformation.hThread);
+ CloseHandle(m_shellProcessInformation.hProcess);
+
+ // Cleanup attribute list
+ if (m_shellStartupInfo.lpAttributeList) {
+ DeleteProcThreadAttributeList(m_shellStartupInfo.lpAttributeList);
+ HeapFree(GetProcessHeap(), 0, m_shellStartupInfo.lpAttributeList);
+ }
+
+ exitCode = true;
+ }
+
+ return exitCode;
+}
+
+IPtyProcess::PtyType ConPtyProcess::type()
+{
+ return PtyType::ConPty;
+}
+
+QString ConPtyProcess::dumpDebugInfo()
+{
+#ifdef PTYQT_DEBUG
+ return QString("PID: %1, Type: %2, Cols: %3, Rows: %4")
+ .arg(m_pid).arg(type())
+ .arg(m_size.first).arg(m_size.second);
+#else
+ return QString("Nothing...");
+#endif
+}
+
+QIODevice *ConPtyProcess::notifier()
+{
+ return &m_buffer;
+}
+
+QByteArray ConPtyProcess::readAll()
+{
+ QByteArray result;
+ {
+ QMutexLocker locker(&m_bufferMutex);
+ result.swap(m_buffer.m_readBuffer);
+ }
+ return result;
+}
+
+qint64 ConPtyProcess::write(const QByteArray &byteArray)
+{
+ DWORD dwBytesWritten{};
+ WriteFile(m_hPipeOut, byteArray.data(), byteArray.size(), &dwBytesWritten, NULL);
+ return dwBytesWritten;
+}
+
+bool ConPtyProcess::isAvailable()
+{
+#ifdef TOO_OLD_WINSDK
+ return false; //very importnant! ConPty can be built, but it doesn't work if built with old sdk and Win10 < 1903
+#endif
+
+ qint32 buildNumber = QSysInfo::kernelVersion().split(".").last().toInt();
+ if (buildNumber < CONPTY_MINIMAL_WINDOWS_VERSION)
+ return false;
+ return WindowsContext::instance().init();
+}
+
+void ConPtyProcess::moveToThread(QThread *targetThread)
+{
+ //nothing for now...
+}
diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.h b/src/libs/3rdparty/libptyqt/conptyprocess.h
new file mode 100644
index 0000000000..aaf56fe76f
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/conptyprocess.h
@@ -0,0 +1,90 @@
+#ifndef CONPTYPROCESS_H
+#define CONPTYPROCESS_H
+
+#include "iptyprocess.h"
+#include <windows.h>
+#include <process.h>
+#include <stdio.h>
+
+#include <QIODevice>
+#include <QLibrary>
+#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;
+
+#define TOO_OLD_WINSDK
+#endif
+
+class QWinEventNotifier;
+
+class PtyBuffer : public QIODevice
+{
+ friend class ConPtyProcess;
+ Q_OBJECT
+public:
+
+ //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() const { return true; }
+ qint64 bytesAvailable() const { return m_readBuffer.size(); }
+ qint64 size() const { return m_readBuffer.size(); }
+
+ void emitReadyRead()
+ {
+ emit readyRead();
+ }
+
+private:
+ QByteArray m_readBuffer;
+};
+
+class ConPtyProcess : public IPtyProcess
+{
+public:
+ ConPtyProcess();
+ ~ConPtyProcess();
+
+ bool startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ 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);
+ static 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:
+ HPCON m_ptyHandler{INVALID_HANDLE_VALUE};
+ HANDLE m_hPipeIn{INVALID_HANDLE_VALUE}, m_hPipeOut{INVALID_HANDLE_VALUE};
+
+ QThread *m_readThread{nullptr};
+ QMutex m_bufferMutex;
+ PtyBuffer m_buffer;
+ bool m_aboutToDestruct{false};
+ PROCESS_INFORMATION m_shellProcessInformation{};
+ QWinEventNotifier *m_shellCloseWaitNotifier{nullptr};
+ STARTUPINFOEX m_shellStartupInfo{};
+};
+
+#endif // CONPTYPROCESS_H
diff --git a/src/libs/3rdparty/libptyqt/iptyprocess.h b/src/libs/3rdparty/libptyqt/iptyprocess.h
new file mode 100644
index 0000000000..6005b0efde
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/iptyprocess.h
@@ -0,0 +1,62 @@
+#ifndef IPTYPROCESS_H
+#define IPTYPROCESS_H
+
+#include <QDebug>
+#include <QString>
+#include <QThread>
+
+#define CONPTY_MINIMAL_WINDOWS_VERSION 18309
+
+class IPtyProcess
+{
+public:
+ enum PtyType { UnixPty = 0, WinPty = 1, ConPty = 2, AutoPty = 3 };
+ enum PtyInputFlag { None = 0x0, InputModeHidden = 0x1, };
+ Q_DECLARE_FLAGS(PtyInputFlags, PtyInputFlag)
+
+ IPtyProcess() = default;
+ IPtyProcess(const IPtyProcess &) = delete;
+ IPtyProcess &operator=(const IPtyProcess &) = delete;
+
+ virtual ~IPtyProcess() {}
+
+ virtual bool startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows)
+ = 0;
+ virtual bool resize(qint16 cols, qint16 rows) = 0;
+ virtual bool kill() = 0;
+ virtual PtyType type() = 0;
+ virtual QString dumpDebugInfo() = 0;
+ virtual QIODevice *notifier() = 0;
+ virtual QByteArray readAll() = 0;
+ virtual qint64 write(const QByteArray &byteArray) = 0;
+ virtual void moveToThread(QThread *targetThread) = 0;
+ qint64 pid() { return m_pid; }
+ QPair<qint16, qint16> size() { return m_size; }
+ const QString lastError() { return m_lastError; }
+ int exitCode() { return m_exitCode; }
+ bool toggleTrace()
+ {
+ m_trace = !m_trace;
+ return m_trace;
+ }
+
+ PtyInputFlags inputFlags() { return m_inputFlags; }
+
+protected:
+ QString m_shellPath;
+ QString m_lastError;
+ qint64 m_pid{0};
+ int m_exitCode{0};
+ QPair<qint16, qint16> m_size; //cols / rows
+ bool m_trace{false};
+ PtyInputFlags m_inputFlags;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(IPtyProcess::PtyInputFlags)
+
+#endif // IPTYPROCESS_H
diff --git a/src/libs/3rdparty/libptyqt/ptyqt.cpp b/src/libs/3rdparty/libptyqt/ptyqt.cpp
new file mode 100644
index 0000000000..06fe449819
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/ptyqt.cpp
@@ -0,0 +1,56 @@
+#include "ptyqt.h"
+#include <utility>
+
+#ifdef Q_OS_WIN
+#include "winptyprocess.h"
+#include "conptyprocess.h"
+#endif
+
+#ifdef Q_OS_UNIX
+#include "unixptyprocess.h"
+#endif
+
+bool PtyQt::isUsingConPTY()
+{
+#ifdef Q_OS_WIN
+ if (ConPtyProcess::isAvailable() && qgetenv("QTC_USE_WINPTY").isEmpty())
+ return true;
+#endif
+
+ return false;
+}
+
+IPtyProcess *PtyQt::createPtyProcess(IPtyProcess::PtyType ptyType)
+{
+ switch (ptyType)
+ {
+#ifdef Q_OS_WIN
+ case IPtyProcess::PtyType::WinPty:
+ return new WinPtyProcess();
+ break;
+ case IPtyProcess::PtyType::ConPty:
+ return new ConPtyProcess();
+ break;
+#endif
+#ifdef Q_OS_UNIX
+ case IPtyProcess::PtyType::UnixPty:
+ return new UnixPtyProcess();
+ break;
+#endif
+ case IPtyProcess::PtyType::AutoPty:
+ default:
+ break;
+ }
+
+#ifdef Q_OS_WIN
+ if (isUsingConPTY())
+ return new ConPtyProcess();
+ else
+ return new WinPtyProcess();
+#endif
+#ifdef Q_OS_UNIX
+ return new UnixPtyProcess();
+#endif
+}
+
+
diff --git a/src/libs/3rdparty/libptyqt/ptyqt.h b/src/libs/3rdparty/libptyqt/ptyqt.h
new file mode 100644
index 0000000000..85f27b33a9
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/ptyqt.h
@@ -0,0 +1,14 @@
+#ifndef PTYQT_H
+#define PTYQT_H
+
+#include "iptyprocess.h"
+
+class PtyQt
+{
+public:
+ static IPtyProcess *createPtyProcess(IPtyProcess::PtyType ptyType);
+
+ static bool isUsingConPTY();
+};
+
+#endif // PTYQT_H
diff --git a/src/libs/3rdparty/libptyqt/ptyqt.qbs b/src/libs/3rdparty/libptyqt/ptyqt.qbs
new file mode 100644
index 0000000000..3b56dd8e46
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/ptyqt.qbs
@@ -0,0 +1,40 @@
+QtcLibrary {
+ name: "ptyqt"
+ type: "staticlibrary"
+
+ Depends { name: "Qt.core" }
+ Depends { name: "Qt.network"; condition: qbs.targetOS.contains("windows") }
+ Depends { name: "winpty"; condition: qbs.targetOS.contains("windows") }
+
+ files: [
+ "iptyprocess.h",
+ "ptyqt.cpp",
+ "ptyqt.h",
+ ]
+
+ Group {
+ name: "ptyqt UNIX files"
+ condition: qbs.targetOS.contains("unix")
+ files: [
+ "unixptyprocess.cpp",
+ "unixptyprocess.h",
+ ]
+ }
+
+ Group {
+ name: "ptyqt Windows files"
+ condition: qbs.targetOS.contains("windows")
+ files: [
+ "conptyprocess.cpp",
+ "conptyprocess.h",
+ "winptyprocess.cpp",
+ "winptyprocess.h",
+ ]
+ }
+
+ Export {
+ Depends { name: "cpp" }
+ Depends { name: "winpty"; condition: qbs.targetOS.contains("windows") }
+ cpp.includePaths: exportingProduct.sourceDirectory
+ }
+}
diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp
new file mode 100644
index 0000000000..d76349a49e
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp
@@ -0,0 +1,362 @@
+#include "unixptyprocess.h"
+#include <QStandardPaths>
+
+#include <errno.h>
+#include <termios.h>
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_FREEBSD)
+#include <utmpx.h>
+#endif
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <QCoreApplication>
+#include <QFileInfo>
+
+UnixPtyProcess::UnixPtyProcess()
+ : IPtyProcess()
+ , m_readMasterNotify(0)
+{
+ m_shellProcess.setWorkingDirectory(
+ QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
+}
+
+UnixPtyProcess::~UnixPtyProcess()
+{
+ kill();
+}
+
+bool UnixPtyProcess::startProcess(const QString &shellPath,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows)
+{
+ if (!isAvailable()) {
+ m_lastError = QString("UnixPty Error: unavailable");
+ return false;
+ }
+
+ if (m_shellProcess.state() == QProcess::Running)
+ return false;
+
+ QFileInfo fi(shellPath);
+ if (fi.isRelative() || !QFile::exists(shellPath)) {
+ //todo add auto-find executable in PATH env var
+ m_lastError = QString("UnixPty Error: shell file path must be absolute");
+ return false;
+ }
+
+ m_shellPath = shellPath;
+ m_size = QPair<qint16, qint16>(cols, rows);
+
+ int rc = 0;
+
+ m_shellProcess.m_handleMaster = ::posix_openpt(O_RDWR | O_NOCTTY);
+
+ int flags = fcntl(m_shellProcess.m_handleMaster, F_GETFL, 0);
+ fcntl(m_shellProcess.m_handleMaster, F_SETFL, flags | O_NONBLOCK);
+
+ if (m_shellProcess.m_handleMaster <= 0) {
+ m_lastError = QString("UnixPty Error: unable to open master -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ m_shellProcess.m_handleSlaveName = QLatin1String(ptsname(m_shellProcess.m_handleMaster));
+ if (m_shellProcess.m_handleSlaveName.isEmpty()) {
+ m_lastError = QString("UnixPty Error: unable to get slave name -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ rc = grantpt(m_shellProcess.m_handleMaster);
+ if (rc != 0) {
+ m_lastError
+ = QString("UnixPty Error: unable to change perms for slave -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ rc = unlockpt(m_shellProcess.m_handleMaster);
+ if (rc != 0) {
+ m_lastError = QString("UnixPty Error: unable to unlock slave -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ m_shellProcess.m_handleSlave = ::open(m_shellProcess.m_handleSlaveName.toLatin1().data(),
+ O_RDWR | O_NOCTTY);
+ if (m_shellProcess.m_handleSlave < 0) {
+ m_lastError = QString("UnixPty Error: unable to open slave -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ rc = fcntl(m_shellProcess.m_handleMaster, F_SETFD, FD_CLOEXEC);
+ if (rc == -1) {
+ m_lastError
+ = QString("UnixPty Error: unable to set flags for master -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ rc = fcntl(m_shellProcess.m_handleSlave, F_SETFD, FD_CLOEXEC);
+ if (rc == -1) {
+ m_lastError
+ = QString("UnixPty Error: unable to set flags for slave -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ struct ::termios ttmode;
+ rc = tcgetattr(m_shellProcess.m_handleMaster, &ttmode);
+ if (rc != 0) {
+ m_lastError = QString("UnixPty Error: termios fail -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ ttmode.c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
+#if defined(IUTF8)
+ ttmode.c_iflag |= IUTF8;
+#endif
+
+ ttmode.c_oflag = OPOST | ONLCR;
+ ttmode.c_cflag = CREAD | CS8 | HUPCL;
+ ttmode.c_lflag = ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL;
+
+ ttmode.c_cc[VEOF] = 4;
+ ttmode.c_cc[VEOL] = -1;
+ ttmode.c_cc[VEOL2] = -1;
+ ttmode.c_cc[VERASE] = 0x7f;
+ ttmode.c_cc[VWERASE] = 23;
+ ttmode.c_cc[VKILL] = 21;
+ ttmode.c_cc[VREPRINT] = 18;
+ ttmode.c_cc[VINTR] = 3;
+ ttmode.c_cc[VQUIT] = 0x1c;
+ ttmode.c_cc[VSUSP] = 26;
+ ttmode.c_cc[VSTART] = 17;
+ ttmode.c_cc[VSTOP] = 19;
+ ttmode.c_cc[VLNEXT] = 22;
+ ttmode.c_cc[VDISCARD] = 15;
+ ttmode.c_cc[VMIN] = 1;
+ ttmode.c_cc[VTIME] = 0;
+
+#if (__APPLE__)
+ ttmode.c_cc[VDSUSP] = 25;
+ ttmode.c_cc[VSTATUS] = 20;
+#endif
+
+ cfsetispeed(&ttmode, B38400);
+ cfsetospeed(&ttmode, B38400);
+
+ rc = tcsetattr(m_shellProcess.m_handleMaster, TCSANOW, &ttmode);
+ if (rc != 0) {
+ m_lastError
+ = QString("UnixPty Error: unabble to set associated params -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ m_readMasterNotify = new QSocketNotifier(m_shellProcess.m_handleMaster,
+ QSocketNotifier::Read,
+ &m_shellProcess);
+ m_readMasterNotify->setEnabled(true);
+ m_readMasterNotify->moveToThread(m_shellProcess.thread());
+ QObject::connect(m_readMasterNotify, &QSocketNotifier::activated, [this](int socket) {
+ Q_UNUSED(socket)
+
+ const size_t maxRead = 16 * 1024;
+ static std::array<char, maxRead> buffer;
+
+ int len = ::read(m_shellProcess.m_handleMaster, buffer.data(), buffer.size());
+
+ struct termios termAttributes;
+ tcgetattr(m_shellProcess.m_handleMaster, &termAttributes);
+ const bool isPasswordEntry = !(termAttributes.c_lflag & ECHO) && (termAttributes.c_lflag & ICANON);
+ m_inputFlags.setFlag(PtyInputFlag::InputModeHidden, isPasswordEntry);
+
+ if (len > 0) {
+ m_shellReadBuffer.append(buffer.data(), len);
+ m_shellProcess.emitReadyRead();
+ }
+ });
+
+ QObject::connect(&m_shellProcess, &QProcess::finished, &m_shellProcess, [this](int exitCode) {
+ m_exitCode = exitCode;
+ emit m_shellProcess.aboutToClose();
+ m_readMasterNotify->disconnect();
+ });
+
+ QProcessEnvironment env;
+ for (const QString &envEntry : environment) {
+ const int idx = envEntry.indexOf('=');
+ if (idx != -1)
+ env.insert(envEntry.left(idx), envEntry.mid(idx + 1));
+ else
+ env.insert(envEntry, QString());
+ }
+
+ m_shellProcess.setWorkingDirectory(workingDir);
+ m_shellProcess.setProcessEnvironment(env);
+ m_shellProcess.setReadChannel(QProcess::StandardOutput);
+ m_shellProcess.start(m_shellPath, arguments);
+ if (!m_shellProcess.waitForStarted())
+ return false;
+
+ m_pid = m_shellProcess.processId();
+
+ resize(cols, rows);
+
+ return true;
+}
+
+bool UnixPtyProcess::resize(qint16 cols, qint16 rows)
+{
+ struct winsize winp;
+ winp.ws_col = cols;
+ winp.ws_row = rows;
+ winp.ws_xpixel = 0;
+ winp.ws_ypixel = 0;
+
+ bool res = ((ioctl(m_shellProcess.m_handleMaster, TIOCSWINSZ, &winp) != -1)
+ && (ioctl(m_shellProcess.m_handleSlave, TIOCSWINSZ, &winp) != -1));
+
+ if (res) {
+ m_size = QPair<qint16, qint16>(cols, rows);
+ }
+
+ return res;
+}
+
+bool UnixPtyProcess::kill()
+{
+ m_shellProcess.m_handleSlaveName = QString();
+ if (m_shellProcess.m_handleSlave >= 0) {
+ ::close(m_shellProcess.m_handleSlave);
+ m_shellProcess.m_handleSlave = -1;
+ }
+ if (m_shellProcess.m_handleMaster >= 0) {
+ ::close(m_shellProcess.m_handleMaster);
+ m_shellProcess.m_handleMaster = -1;
+ }
+
+ if (m_shellProcess.state() == QProcess::Running) {
+ m_readMasterNotify->disconnect();
+ m_readMasterNotify->deleteLater();
+
+ m_shellProcess.terminate();
+ m_shellProcess.waitForFinished(1000);
+
+ if (m_shellProcess.state() == QProcess::Running) {
+ QProcess::startDetached(QString("kill -9 %1").arg(pid()));
+ m_shellProcess.kill();
+ m_shellProcess.waitForFinished(1000);
+ }
+
+ return (m_shellProcess.state() == QProcess::NotRunning);
+ }
+ return false;
+}
+
+IPtyProcess::PtyType UnixPtyProcess::type()
+{
+ return IPtyProcess::UnixPty;
+}
+
+QString UnixPtyProcess::dumpDebugInfo()
+{
+#ifdef PTYQT_DEBUG
+ return QString("PID: %1, In: %2, Out: %3, Type: %4, Cols: %5, Rows: %6, IsRunning: %7, Shell: "
+ "%8, SlaveName: %9")
+ .arg(m_pid)
+ .arg(m_shellProcess.m_handleMaster)
+ .arg(m_shellProcess.m_handleSlave)
+ .arg(type())
+ .arg(m_size.first)
+ .arg(m_size.second)
+ .arg(m_shellProcess.state() == QProcess::Running)
+ .arg(m_shellPath)
+ .arg(m_shellProcess.m_handleSlaveName);
+#else
+ return QString("Nothing...");
+#endif
+}
+
+QIODevice *UnixPtyProcess::notifier()
+{
+ return &m_shellProcess;
+}
+
+QByteArray UnixPtyProcess::readAll()
+{
+ QByteArray tmpBuffer = m_shellReadBuffer;
+ m_shellReadBuffer.clear();
+ return tmpBuffer;
+}
+
+qint64 UnixPtyProcess::write(const QByteArray &byteArray)
+{
+ return ::write(m_shellProcess.m_handleMaster, byteArray.constData(), byteArray.size());
+}
+
+bool UnixPtyProcess::isAvailable()
+{
+ //todo check something more if required
+ return true;
+}
+
+void UnixPtyProcess::moveToThread(QThread *targetThread)
+{
+ m_shellProcess.moveToThread(targetThread);
+}
+
+void ShellProcess::configChildProcess()
+{
+ dup2(m_handleSlave, STDIN_FILENO);
+ dup2(m_handleSlave, STDOUT_FILENO);
+ dup2(m_handleSlave, STDERR_FILENO);
+
+ pid_t sid = setsid();
+ ioctl(m_handleSlave, TIOCSCTTY, 0);
+ tcsetpgrp(m_handleSlave, sid);
+
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_FREEBSD)
+ // on Android imposible to put record to the 'utmp' file
+ struct utmpx utmpxInfo;
+ memset(&utmpxInfo, 0, sizeof(utmpxInfo));
+
+ strncpy(utmpxInfo.ut_user, qgetenv("USER"), sizeof(utmpxInfo.ut_user) - 1);
+
+ QString device(m_handleSlaveName);
+ if (device.startsWith("/dev/"))
+ device = device.mid(5);
+
+ const auto deviceAsLatin1 = device.toLatin1();
+ const char *d = deviceAsLatin1.constData();
+
+ strncpy(utmpxInfo.ut_line, d, sizeof(utmpxInfo.ut_line) - 1);
+
+ strncpy(utmpxInfo.ut_id, d + strlen(d) - sizeof(utmpxInfo.ut_id), sizeof(utmpxInfo.ut_id));
+
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+ utmpxInfo.ut_tv.tv_sec = tv.tv_sec;
+ utmpxInfo.ut_tv.tv_usec = tv.tv_usec;
+
+ utmpxInfo.ut_type = USER_PROCESS;
+ utmpxInfo.ut_pid = getpid();
+
+ utmpxname(_PATH_UTMPX);
+ setutxent();
+ pututxline(&utmpxInfo);
+ endutxent();
+
+#if !defined(Q_OS_UNIX)
+ updwtmpx(_PATH_UTMPX, &loginInfo);
+#endif
+
+#endif
+}
diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.h b/src/libs/3rdparty/libptyqt/unixptyprocess.h
new file mode 100644
index 0000000000..f7e3a2e528
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/unixptyprocess.h
@@ -0,0 +1,66 @@
+#ifndef UNIXPTYPROCESS_H
+#define UNIXPTYPROCESS_H
+
+#include "iptyprocess.h"
+#include <QProcess>
+#include <QSocketNotifier>
+
+// support for build with MUSL on Alpine Linux
+#ifndef _PATH_UTMPX
+#include <sys/time.h>
+#define _PATH_UTMPX "/var/log/utmp"
+#endif
+
+class ShellProcess : public QProcess
+{
+ friend class UnixPtyProcess;
+ Q_OBJECT
+public:
+ ShellProcess()
+ : QProcess()
+ , m_handleMaster(-1)
+ , m_handleSlave(-1)
+ {
+ setProcessChannelMode(QProcess::SeparateChannels);
+ setChildProcessModifier([this]() { configChildProcess(); });
+ }
+
+ void emitReadyRead() { emit readyRead(); }
+
+protected:
+ void configChildProcess();
+
+private:
+ int m_handleMaster, m_handleSlave;
+ QString m_handleSlaveName;
+};
+
+class UnixPtyProcess : public IPtyProcess
+{
+public:
+ UnixPtyProcess();
+ virtual ~UnixPtyProcess();
+
+ virtual bool startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows);
+ virtual bool resize(qint16 cols, qint16 rows);
+ virtual bool kill();
+ virtual PtyType type();
+ virtual QString dumpDebugInfo();
+ virtual QIODevice *notifier();
+ virtual QByteArray readAll();
+ virtual qint64 write(const QByteArray &byteArray);
+ static bool isAvailable();
+ void moveToThread(QThread *targetThread);
+
+private:
+ ShellProcess m_shellProcess;
+ QSocketNotifier *m_readMasterNotify;
+ QByteArray m_shellReadBuffer;
+};
+
+#endif // UNIXPTYPROCESS_H
diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.cpp b/src/libs/3rdparty/libptyqt/winptyprocess.cpp
new file mode 100644
index 0000000000..7a781bc195
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/winptyprocess.cpp
@@ -0,0 +1,278 @@
+#include "winptyprocess.h"
+#include <QFile>
+#include <QFileInfo>
+#include <sstream>
+#include <QCoreApplication>
+#include <QLocalSocket>
+#include <QWinEventNotifier>
+
+#define DEBUG_VAR_LEGACY "WINPTYDBG"
+#define DEBUG_VAR_ACTUAL "WINPTY_DEBUG"
+#define SHOW_CONSOLE_VAR "WINPTY_SHOW_CONSOLE"
+#define WINPTY_AGENT_NAME "winpty-agent.exe"
+#define WINPTY_DLL_NAME "winpty.dll"
+
+QString castErrorToString(winpty_error_ptr_t error_ptr)
+{
+ return QString::fromStdWString(winpty_error_msg(error_ptr));
+}
+
+WinPtyProcess::WinPtyProcess()
+ : IPtyProcess()
+ , m_ptyHandler(nullptr)
+ , m_innerHandle(nullptr)
+ , m_inSocket(nullptr)
+ , m_outSocket(nullptr)
+{
+#ifdef PTYQT_DEBUG
+ m_trace = true;
+#endif
+}
+
+WinPtyProcess::~WinPtyProcess()
+{
+ kill();
+}
+
+bool WinPtyProcess::startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows)
+{
+ if (!isAvailable())
+ {
+ m_lastError = QString("WinPty Error: winpty-agent.exe or winpty.dll not found!");
+ return false;
+ }
+
+ //already running
+ if (m_ptyHandler != nullptr)
+ return false;
+
+ QFileInfo fi(executable);
+ if (fi.isRelative() || !QFile::exists(executable))
+ {
+ //todo add auto-find executable in PATH env var
+ m_lastError = QString("WinPty Error: shell file path must be absolute");
+ return false;
+ }
+
+ m_shellPath = executable;
+ m_shellPath.replace('/', '\\');
+ m_size = QPair<qint16, qint16>(cols, rows);
+
+#ifdef PTYQT_DEBUG
+ if (m_trace)
+ {
+ environment.append(QString("%1=1").arg(DEBUG_VAR_LEGACY));
+ environment.append(QString("%1=trace").arg(DEBUG_VAR_ACTUAL));
+ environment.append(QString("%1=1").arg(SHOW_CONSOLE_VAR));
+ SetEnvironmentVariableA(DEBUG_VAR_LEGACY, "1");
+ SetEnvironmentVariableA(DEBUG_VAR_ACTUAL, "trace");
+ SetEnvironmentVariableA(SHOW_CONSOLE_VAR, "1");
+ }
+#endif
+
+ //env
+ std::wstringstream envBlock;
+ for (const QString &line : environment)
+ {
+ envBlock << line.toStdWString() << L'\0';
+ }
+ std::wstring env = envBlock.str();
+
+ //create start config
+ winpty_error_ptr_t errorPtr = nullptr;
+ winpty_config_t* startConfig = winpty_config_new(0, &errorPtr);
+ if (startConfig == nullptr)
+ {
+ m_lastError = QString("WinPty Error: create start config -> %1").arg(castErrorToString(errorPtr));
+ return false;
+ }
+ winpty_error_free(errorPtr);
+
+ //set params
+ winpty_config_set_initial_size(startConfig, cols, rows);
+ winpty_config_set_mouse_mode(startConfig, WINPTY_MOUSE_MODE_AUTO);
+ //winpty_config_set_agent_timeout();
+
+ //start agent
+ m_ptyHandler = winpty_open(startConfig, &errorPtr);
+ winpty_config_free(startConfig); //start config is local var, free it after use
+
+ if (m_ptyHandler == nullptr)
+ {
+ m_lastError = QString("WinPty Error: start agent -> %1").arg(castErrorToString(errorPtr));
+ return false;
+ }
+ winpty_error_free(errorPtr);
+
+ //create spawn config
+ winpty_spawn_config_t* spawnConfig = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, m_shellPath.toStdWString().c_str(),
+ //commandLine.toStdWString().c_str(), cwd.toStdWString().c_str(),
+ arguments.join(" ").toStdWString().c_str(), workingDir.toStdWString().c_str(),
+ env.c_str(),
+ &errorPtr);
+
+ if (spawnConfig == nullptr)
+ {
+ m_lastError = QString("WinPty Error: create spawn config -> %1").arg(castErrorToString(errorPtr));
+ return false;
+ }
+ winpty_error_free(errorPtr);
+
+ //spawn the new process
+ BOOL spawnSuccess = winpty_spawn(m_ptyHandler, spawnConfig, &m_innerHandle, nullptr, nullptr, &errorPtr);
+ winpty_spawn_config_free(spawnConfig); //spawn config is local var, free it after use
+ if (!spawnSuccess)
+ {
+ m_lastError = QString("WinPty Error: start terminal process -> %1").arg(castErrorToString(errorPtr));
+ return false;
+ }
+ winpty_error_free(errorPtr);
+
+ m_pid = (int)GetProcessId(m_innerHandle);
+
+ m_outSocket = new QLocalSocket();
+
+ // Notify when the shell process has been terminated
+ m_shellCloseWaitNotifier = new QWinEventNotifier(m_innerHandle, notifier());
+ QObject::connect(m_shellCloseWaitNotifier,
+ &QWinEventNotifier::activated,
+ notifier(),
+ [this](HANDLE hEvent) {
+ DWORD exitCode = 0;
+ GetExitCodeProcess(hEvent, &exitCode);
+ m_exitCode = exitCode;
+ // Do not respawn if the object is about to be destructed
+ if (!m_aboutToDestruct)
+ emit notifier()->aboutToClose();
+ m_shellCloseWaitNotifier->setEnabled(false);
+ });
+
+ //get pipe names
+ LPCWSTR conInPipeName = winpty_conin_name(m_ptyHandler);
+ m_conInName = QString::fromStdWString(std::wstring(conInPipeName));
+ m_inSocket = new QLocalSocket();
+ m_inSocket->connectToServer(m_conInName, QIODevice::WriteOnly);
+ m_inSocket->waitForConnected();
+
+ LPCWSTR conOutPipeName = winpty_conout_name(m_ptyHandler);
+ m_conOutName = QString::fromStdWString(std::wstring(conOutPipeName));
+ m_outSocket->connectToServer(m_conOutName, QIODevice::ReadOnly);
+ m_outSocket->waitForConnected();
+
+ if (m_inSocket->state() != QLocalSocket::ConnectedState && m_outSocket->state() != QLocalSocket::ConnectedState)
+ {
+ m_lastError = QString("WinPty Error: Unable to connect local sockets -> %1 / %2").arg(m_inSocket->errorString()).arg(m_outSocket->errorString());
+ m_inSocket->deleteLater();
+ m_outSocket->deleteLater();
+ m_inSocket = nullptr;
+ m_outSocket = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+bool WinPtyProcess::resize(qint16 cols, qint16 rows)
+{
+ if (m_ptyHandler == nullptr)
+ {
+ return false;
+ }
+
+ bool res = winpty_set_size(m_ptyHandler, cols, rows, nullptr);
+
+ if (res)
+ {
+ m_size = QPair<qint16, qint16>(cols, rows);
+ }
+
+ return res;
+}
+
+bool WinPtyProcess::kill()
+{
+ bool exitCode = false;
+ if (m_innerHandle != nullptr && m_ptyHandler != nullptr) {
+ m_aboutToDestruct = true;
+ //disconnect all signals (readyRead, ...)
+ m_inSocket->disconnect();
+ m_outSocket->disconnect();
+
+ //disconnect for server
+ m_inSocket->disconnectFromServer();
+ m_outSocket->disconnectFromServer();
+
+ m_inSocket->deleteLater();
+ m_outSocket->deleteLater();
+
+ m_inSocket = nullptr;
+ m_outSocket = nullptr;
+
+ winpty_free(m_ptyHandler);
+ exitCode = CloseHandle(m_innerHandle);
+
+ delete m_shellCloseWaitNotifier;
+ m_shellCloseWaitNotifier = nullptr;
+
+ m_ptyHandler = nullptr;
+ m_innerHandle = nullptr;
+ m_conInName = QString();
+ m_conOutName = QString();
+ m_pid = 0;
+ }
+ return exitCode;
+}
+
+IPtyProcess::PtyType WinPtyProcess::type()
+{
+ return PtyType::WinPty;
+}
+
+QString WinPtyProcess::dumpDebugInfo()
+{
+#ifdef PTYQT_DEBUG
+ return QString("PID: %1, ConIn: %2, ConOut: %3, Type: %4, Cols: %5, Rows: %6, IsRunning: %7, Shell: %8")
+ .arg(m_pid).arg(m_conInName).arg(m_conOutName).arg(type())
+ .arg(m_size.first).arg(m_size.second).arg(m_ptyHandler != nullptr)
+ .arg(m_shellPath);
+#else
+ return QString("Nothing...");
+#endif
+}
+
+QIODevice *WinPtyProcess::notifier()
+{
+ return m_outSocket;
+}
+
+QByteArray WinPtyProcess::readAll()
+{
+ return m_outSocket->readAll();
+}
+
+qint64 WinPtyProcess::write(const QByteArray &byteArray)
+{
+ return m_inSocket->write(byteArray);
+}
+
+bool WinPtyProcess::isAvailable()
+{
+#ifdef PTYQT_BUILD_STATIC
+ return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME);
+#elif PTYQT_BUILD_DYNAMIC
+ return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME)
+ && QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_DLL_NAME);
+#endif
+ return true;
+}
+
+void WinPtyProcess::moveToThread(QThread *targetThread)
+{
+ m_inSocket->moveToThread(targetThread);
+ m_outSocket->moveToThread(targetThread);
+}
diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.h b/src/libs/3rdparty/libptyqt/winptyprocess.h
new file mode 100644
index 0000000000..ee91fbdf8d
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/winptyprocess.h
@@ -0,0 +1,43 @@
+#ifndef WINPTYPROCESS_H
+#define WINPTYPROCESS_H
+
+#include "iptyprocess.h"
+#include "winpty.h"
+
+class QLocalSocket;
+class QWinEventNotifier;
+
+class WinPtyProcess : public IPtyProcess
+{
+public:
+ WinPtyProcess();
+ ~WinPtyProcess();
+
+ bool startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows);
+ bool resize(qint16 cols, qint16 rows);
+ bool kill();
+ PtyType type();
+ QString dumpDebugInfo();
+ QIODevice *notifier();
+ QByteArray readAll();
+ qint64 write(const QByteArray &byteArray);
+ static bool isAvailable();
+ void moveToThread(QThread *targetThread);
+
+private:
+ winpty_t *m_ptyHandler;
+ HANDLE m_innerHandle;
+ QString m_conInName;
+ QString m_conOutName;
+ QLocalSocket *m_inSocket;
+ QLocalSocket *m_outSocket;
+ bool m_aboutToDestruct{false};
+ QWinEventNotifier* m_shellCloseWaitNotifier;
+};
+
+#endif // WINPTYPROCESS_H