forked from qt-creator/qt-creator
Terminal: Fix terminal resizing on Windows using ConPty
The official ConPty API doesn't give the ability to forward "-- resizeQuirk" to "conhost.exe". Fixing this involved taking parts from the WinConPty implementation (https://github.com/microsoft/terminal/tree/main/src/ winconpty) and porting them to work inside ptyqt. Fixes: QTCREATORBUG-30007 Fixes: QTCREATORBUG-30558 Change-Id: I45e81fa167c88a85b44958eade0d85f7680e8075 Reviewed-by: Cristian Adam <cristian.adam@qt.io> Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
@@ -453,6 +453,19 @@
|
|||||||
"LicenseFile": "src/libs/3rdparty/libptyqt/LICENSE",
|
"LicenseFile": "src/libs/3rdparty/libptyqt/LICENSE",
|
||||||
"Copyright": "Copyright (c) 2019 Vitaly Petrov"
|
"Copyright": "Copyright (c) 2019 Vitaly Petrov"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Id": "ptyqt-conpty",
|
||||||
|
"Name": "Pty-Qt/ConPty",
|
||||||
|
"QDocModule": "qtcreator",
|
||||||
|
"QtParts": ["tools"],
|
||||||
|
"QtUsage": "Used for the integrated terminal.",
|
||||||
|
"Path": "src/libs/3rdparty/libptyqt",
|
||||||
|
"Description": "Major parts of conptyprocess.cpp have been ported from microsofts terminal source code and integrated into ptyqt's class.",
|
||||||
|
"Homepage": "https://github.com/microsoft/terminal",
|
||||||
|
"License": "MIT License",
|
||||||
|
"LicenseFile": "src/libs/3rdparty/libptyqt/LICENSE-CONPTY",
|
||||||
|
"Copyright": "Copyright (c) Microsoft Corporation"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Id": "libvterm",
|
"Id": "libvterm",
|
||||||
"Name": "libvterm",
|
"Name": "libvterm",
|
||||||
|
21
src/libs/3rdparty/libptyqt/LICENSE-CONPTY
vendored
Normal file
21
src/libs/3rdparty/libptyqt/LICENSE-CONPTY
vendored
Normal file
@@ -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.
|
785
src/libs/3rdparty/libptyqt/conptyprocess.cpp
vendored
785
src/libs/3rdparty/libptyqt/conptyprocess.cpp
vendored
@@ -10,8 +10,740 @@
|
|||||||
|
|
||||||
#include <qt_windows.h>
|
#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
|
#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
|
//ConPTY is available only on Windows 10 released after 1903 (19H1) Windows release
|
||||||
class WindowsContext
|
class WindowsContext
|
||||||
{
|
{
|
||||||
@@ -38,30 +770,9 @@ public:
|
|||||||
|
|
||||||
bool init()
|
bool init()
|
||||||
{
|
{
|
||||||
//already initialized
|
createPseudoConsole = (CreatePseudoConsolePtr)ConptyCreatePseudoConsole;
|
||||||
if (createPseudoConsole)
|
resizePseudoConsole = (ResizePseudoConsolePtr)ConptyResizePseudoConsole;
|
||||||
return true;
|
closePseudoConsole = (ClosePseudoConsolePtr)ConptyClosePseudoConsole;
|
||||||
|
|
||||||
//try to load symbols from library
|
|
||||||
//if it fails -> we can't use ConPty API
|
|
||||||
HANDLE kernel32Handle = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
|
||||||
|
|
||||||
if (kernel32Handle != nullptr)
|
|
||||||
{
|
|
||||||
createPseudoConsole = (CreatePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "CreatePseudoConsole");
|
|
||||||
resizePseudoConsole = (ResizePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ResizePseudoConsole");
|
|
||||||
closePseudoConsole = (ClosePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ClosePseudoConsole");
|
|
||||||
if (createPseudoConsole == NULL || resizePseudoConsole == NULL || closePseudoConsole == NULL)
|
|
||||||
{
|
|
||||||
m_lastError = QString("WindowsContext/ConPty error: %1").arg("Invalid on load API functions");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_lastError = QString("WindowsContext/ConPty error: %1").arg("Unable to load kernel32");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -81,6 +792,30 @@ private:
|
|||||||
QString m_lastError;
|
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 ConPtyProcess::createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut, qint16 cols, qint16 rows)
|
||||||
{
|
{
|
||||||
@@ -93,7 +828,7 @@ HRESULT ConPtyProcess::createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn
|
|||||||
CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0))
|
CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0))
|
||||||
{
|
{
|
||||||
// Create the Pseudo Console of the required size, attached to the PTY-end of the pipes
|
// Create the Pseudo Console of the required size, attached to the PTY-end of the pipes
|
||||||
hr = WindowsContext::instance().createPseudoConsole({cols, rows}, hPipePTYIn, hPipePTYOut, 0, phPC);
|
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
|
// 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
|
// because the handles are dup'ed into the ConHost and will be released
|
||||||
|
Reference in New Issue
Block a user