terminal/src/tools/vtpipeterm/VtConsole.cpp
N d09fdd61cb
Change backslashes in include statements to forward slashes (#8205)
Many include statements use forward slashes, while others use backwards
slashes. This is inconsistent formatting. For this reason, I changed the
backward slashes to forward slashes since that is the standard.
2020-11-25 21:02:10 +00:00

403 lines
13 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "../../inc/conpty.h"
#include "VtConsole.hpp"
#include <cstdlib> /* srand, rand */
#include <ctime> /* time */
#include <deque>
#include <vector>
#include <string>
#include <sstream>
#include <iomanip>
#include <cassert>
#include <wincon.h>
VtConsole::VtConsole(PipeReadCallback const pfnReadCallback,
bool const fHeadless,
bool const fUseConpty,
COORD const initialSize) :
_pfnReadCallback(pfnReadCallback),
_fHeadless(fHeadless),
_fUseConPty(fUseConpty),
_lastDimensions(initialSize)
{
THROW_HR_IF_NULL(E_INVALIDARG, pfnReadCallback);
}
void VtConsole::spawn()
{
_spawn(L"");
}
void VtConsole::spawn(const std::wstring& command)
{
_spawn(command);
}
// Prepares the `lpAttributeList` member of a STARTUPINFOEX for attaching a
// client application to a pseudoconsole.
// Prior to calling this function, hPty should be initialized with a call to
// CreatePseudoConsole, and the pAttrList should be initialized with a call
// to InitializeProcThreadAttributeList. The caller of
// InitializeProcThreadAttributeList should add one to the dwAttributeCount
// param when creating the attribute list for usage by this function.
HRESULT AttachPseudoConsole(HPCON hPC, LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList)
{
BOOL fSuccess = UpdateProcThreadAttribute(lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hPC,
sizeof(HPCON),
nullptr,
nullptr);
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
// Function Description:
// - Sample function which combines the creation of some basic anonymous pipes
// and passes them to CreatePseudoConsole.
// Arguments:
// - size: The size of the conpty to create, in characters.
// - phInput: Receives the handle to the newly-created anonymous pipe for writing input to the conpty.
// - phOutput: Receives the handle to the newly-created anonymous pipe for reading the output of the conpty.
// - phPty: Receives a token value to identify this conpty
HRESULT CreatePseudoConsoleAndHandles(COORD size,
_In_ DWORD dwFlags,
_Out_ HANDLE* phInput,
_Out_ HANDLE* phOutput,
_Out_ HPCON* phPC)
{
if (phPC == nullptr || phInput == nullptr || phOutput == nullptr)
{
return E_INVALIDARG;
}
HANDLE outPipeOurSide;
HANDLE inPipeOurSide;
HANDLE outPipePseudoConsoleSide;
HANDLE inPipePseudoConsoleSide;
HRESULT hr = S_OK;
if (!CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, nullptr, 0))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr))
{
if (!CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, nullptr, 0))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr))
{
hr = CreatePseudoConsole(size, inPipePseudoConsoleSide, outPipePseudoConsoleSide, dwFlags, phPC);
if (FAILED(hr))
{
CloseHandle(inPipeOurSide);
CloseHandle(outPipeOurSide);
}
else
{
*phInput = inPipeOurSide;
*phOutput = outPipeOurSide;
}
CloseHandle(outPipePseudoConsoleSide);
}
else
{
CloseHandle(inPipeOurSide);
}
CloseHandle(inPipePseudoConsoleSide);
}
return hr;
}
// Method Description:
// - This version of _spawn uses the actual Pty API for creating the conhost,
// independent of the child process. We then attach the client later.
// Arguments:
// - command: commandline of the child application to attach
// Return Value:
// - <none>
void VtConsole::_spawn(const std::wstring& command)
{
if (_fUseConPty)
{
_createPseudoConsole(command);
}
else if (_fHeadless)
{
_createConptyManually(command);
}
else
{
_createConptyViaCommandline(command);
}
_connected = true;
// Create our own output handling thread
// Each console needs to make sure to drain the output from its backing host.
_dwOutputThreadId = (DWORD)-1;
_hOutputThread = CreateThread(nullptr,
0,
StaticOutputThreadProc,
this,
0,
&_dwOutputThreadId);
}
PCWSTR GetCmdLine()
{
return L"conhost.exe";
}
void VtConsole::_createPseudoConsole(const std::wstring& command)
{
bool fSuccess;
THROW_IF_FAILED(CreatePseudoConsoleAndHandles(_lastDimensions, 0, &_inPipe, &_outPipe, &_hPC));
STARTUPINFOEX siEx;
siEx = { 0 };
siEx.StartupInfo.cb = sizeof(STARTUPINFOEX);
size_t size;
InitializeProcThreadAttributeList(nullptr, 1, 0, (PSIZE_T)&size);
BYTE* attrList = new BYTE[size];
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);
fSuccess = InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, (PSIZE_T)&size);
THROW_LAST_ERROR_IF(!fSuccess);
THROW_IF_FAILED(AttachPseudoConsole(_hPC, siEx.lpAttributeList));
std::wstring realCommand = command;
if (realCommand == L"")
{
realCommand = L"cmd.exe";
}
std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(realCommand.length() + 1);
THROW_IF_NULL_ALLOC(mutableCommandline);
HRESULT hr = StringCchCopy(mutableCommandline.get(), realCommand.length() + 1, realCommand.c_str());
THROW_IF_FAILED(hr);
fSuccess = !!CreateProcessW(
nullptr,
mutableCommandline.get(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
true, // bInheritHandles
EXTENDED_STARTUPINFO_PRESENT, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // lpCurrentDirectory
&siEx.StartupInfo, // lpStartupInfo
&_piClient // lpProcessInformation
);
THROW_LAST_ERROR_IF(!fSuccess);
DeleteProcThreadAttributeList(siEx.lpAttributeList);
}
void VtConsole::_createConptyManually(const std::wstring& command)
{
if (_fHeadless)
{
THROW_IF_FAILED(CreateConPty(command,
_lastDimensions.X,
_lastDimensions.Y,
&_inPipe,
&_outPipe,
&_signalPipe,
&_piPty));
}
else
{
_createConptyViaCommandline(command);
}
}
void VtConsole::_createConptyViaCommandline(const std::wstring& command)
{
std::wstring cmdline(GetCmdLine());
if (_fHeadless)
{
cmdline += L" --headless";
}
// Create some anon pipes so we can pass handles down and into the console.
// IMPORTANT NOTE:
// We're creating the pipe here with un-inheritable handles, then marking
// the conhost sides of the pipes as inheritable. We do this because if
// the entire pipe is marked as inheritable, when we pass the handles
// to CreateProcess, at some point the entire pipe object is copied to
// the conhost process, which includes the terminal side of the pipes
// (_inPipe and _outPipe). This means that if we die, there's still
// outstanding handles to our side of the pipes, and those handles are
// in conhost, despite conhost being unable to reference those handles
// and close them.
// CRITICAL: Close our side of the handles. Otherwise you'll get the same
// problem if you close conhost, but not us (the terminal).
// The conhost sides of the pipe will be unique_hfile's so that they'll get
// closed automatically at the end of the method.
wil::unique_hfile outPipeConhostSide;
wil::unique_hfile inPipeConhostSide;
wil::unique_hfile signalPipeConhostSide;
SECURITY_ATTRIBUTES sa;
sa = { 0 };
sa.nLength = sizeof(sa);
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = nullptr;
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&inPipeConhostSide, &_inPipe, &sa, 0));
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&_outPipe, &outPipeConhostSide, &sa, 0));
// Mark inheritable for signal handle when creating. It'll have the same value on the other side.
sa.bInheritHandle = TRUE;
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&signalPipeConhostSide, &_signalPipe, &sa, 0));
THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(inPipeConhostSide.get(), HANDLE_FLAG_INHERIT, 1));
THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(outPipeConhostSide.get(), HANDLE_FLAG_INHERIT, 1));
STARTUPINFO si = { 0 };
si.cb = sizeof(STARTUPINFOW);
si.hStdInput = inPipeConhostSide.get();
si.hStdOutput = outPipeConhostSide.get();
si.hStdError = outPipeConhostSide.get();
si.dwFlags |= STARTF_USESTDHANDLES;
if (!(_lastDimensions.X == 0 && _lastDimensions.Y == 0))
{
// STARTF_USECOUNTCHARS does not work.
// minkernel/console/client/dllinit will write that value to conhost
// during init of a cmdline application, but because we're starting
// conhost directly, that doesn't work for us.
std::wstringstream ss;
ss << L" --width " << _lastDimensions.X;
ss << L" --height " << _lastDimensions.Y;
cmdline += ss.str();
}
// Attach signal handle ID onto command line using string stream for formatting
std::wstringstream signalArg;
signalArg << L" --signal 0x" << std::hex << HandleToUlong(signalPipeConhostSide.get());
cmdline += signalArg.str();
if (command.length() > 0)
{
cmdline += L" -- ";
cmdline += command;
}
else
{
si.dwFlags |= STARTF_USESHOWWINDOW;
si.wShowWindow = SW_MINIMIZE;
}
bool fSuccess = !!CreateProcess(
nullptr,
&cmdline[0],
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
true, // bInheritHandles
0, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // lpCurrentDirectory
&si, // lpStartupInfo
&_piPty // lpProcessInformation
);
if (!fSuccess)
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
std::string msg = "Failed to launch Openconsole";
WriteFile(hOut, msg.c_str(), (DWORD)msg.length(), nullptr, nullptr);
}
}
void VtConsole::activate()
{
_active = true;
}
void VtConsole::deactivate()
{
_active = false;
}
DWORD WINAPI VtConsole::StaticOutputThreadProc(LPVOID lpParameter)
{
VtConsole* const pInstance = (VtConsole*)lpParameter;
return pInstance->_OutputThread();
}
DWORD VtConsole::_OutputThread()
{
BYTE buffer[256];
DWORD dwRead;
while (true)
{
dwRead = 0;
bool fSuccess = false;
fSuccess = !!ReadFile(this->outPipe(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr);
if (!fSuccess)
{
HRESULT hr = GetLastError();
exit(hr);
}
if (this->_active)
{
_pfnReadCallback(buffer, dwRead);
}
}
}
bool VtConsole::Repaint()
{
std::string seq = "\x1b[7t";
return WriteInput(seq);
}
bool VtConsole::Resize(const unsigned short rows, const unsigned short cols)
{
if (_fUseConPty)
{
return SUCCEEDED(ResizePseudoConsole(_hPC, { (SHORT)cols, (SHORT)rows }));
}
else
{
return SignalResizeWindow(_signalPipe, cols, rows);
}
}
HANDLE VtConsole::inPipe()
{
return _inPipe;
}
HANDLE VtConsole::outPipe()
{
return _outPipe;
}
void VtConsole::signalWindow(unsigned short sx, unsigned short sy)
{
Resize(sy, sx);
}
bool VtConsole::WriteInput(std::string& seq)
{
bool fSuccess = !!WriteFile(inPipe(), seq.c_str(), (DWORD)seq.length(), nullptr, nullptr);
if (!fSuccess)
{
HRESULT hr = GetLastError();
exit(hr);
}
return fSuccess;
}