зеркало из https://github.com/microsoft/terminal.git
Modernize VtPipeTerm (#17647)
This shortens VtPipeTerm quite a bit, which used to have various debug flags and modes. I kept the `--out` flag to redirect the output to a file, but I removed the `--debug` (pipe the output through WSL and show escape sequences visually) and `--headless` (hide conpty) flags. I did this, because VtPipeTerm always used the system ConPTY API but I needed it to use my local OpenConsole. I also wanted it to use overlapped IO for testing but found that it was too difficult to refactor make that work. I also noticed that the project was the only holdout for `conpty.h` which had to be kept in sync with `winconpty.h`.
This commit is contained in:
Родитель
1f71568c2a
Коммит
760daa642e
|
@ -174,6 +174,7 @@ PALLOC
|
|||
PATINVERT
|
||||
PEXPLICIT
|
||||
PICKFOLDERS
|
||||
PINPUT
|
||||
pmr
|
||||
ptstr
|
||||
QUERYENDSESSION
|
||||
|
|
|
@ -913,6 +913,7 @@ Keymapping
|
|||
keyscan
|
||||
keystate
|
||||
keyups
|
||||
Kickstart
|
||||
KILLACTIVE
|
||||
KILLFOCUS
|
||||
kinda
|
||||
|
|
157
src/inc/conpty.h
157
src/inc/conpty.h
|
@ -1,157 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <strsafe.h>
|
||||
#include <memory>
|
||||
#pragma once
|
||||
|
||||
const unsigned int PTY_SIGNAL_CLEAR_WINDOW = 2u;
|
||||
const unsigned int PTY_SIGNAL_RESIZE_WINDOW = 8u;
|
||||
|
||||
HRESULT CreateConPty(const std::wstring& cmdline, // _In_
|
||||
const unsigned short w, // _In_
|
||||
const unsigned short h, // _In_
|
||||
HANDLE* const hInput, // _Out_
|
||||
HANDLE* const hOutput, // _Out_
|
||||
HANDLE* const hSignal, // _Out_
|
||||
PROCESS_INFORMATION* const piPty); // _Out_
|
||||
|
||||
bool SignalResizeWindow(const HANDLE hSignal,
|
||||
const unsigned short w,
|
||||
const unsigned short h);
|
||||
|
||||
// Function Description:
|
||||
// - Creates a headless conhost in "pty mode" and launches the given commandline
|
||||
// attached to the conhost. Gives back handles to three different pipes:
|
||||
// * hInput: The caller can write input to the conhost, encoded in utf-8, on
|
||||
// this pipe. For keys that don't have character representations, the
|
||||
// caller should use the `TERM=xterm` VT sequences for encoding the input.
|
||||
// * hOutput: The caller should read from this pipe. The headless conhost will
|
||||
// "render" its state to a stream of utf-8 encoded text with VT sequences.
|
||||
// * hSignal: The caller can use this to resize the size of the underlying PTY
|
||||
// using the SignalResizeWindow function.
|
||||
// Arguments:
|
||||
// - cmdline: The commandline to launch as a console process attached to the pty
|
||||
// that's created.
|
||||
// - w: The initial width of the pty, in characters
|
||||
// - h: The initial height of the pty, in characters
|
||||
// - hInput: A handle to the pipe for writing input to the pty.
|
||||
// - hOutput: A handle to the pipe for reading the output of the pty.
|
||||
// - hSignal: A handle to the pipe for writing signal messages to the pty.
|
||||
// - piPty: The PROCESS_INFORMATION of the pty process. NOTE: This is *not* the
|
||||
// PROCESS_INFORMATION of the process that's created as a result the cmdline.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, or an appropriate HRESULT for failing format the
|
||||
// commandline or failing to launch the conhost
|
||||
__declspec(noinline) inline HRESULT CreateConPty(const std::wstring& cmdline,
|
||||
const unsigned short w,
|
||||
const unsigned short h,
|
||||
HANDLE* const hInput,
|
||||
HANDLE* const hOutput,
|
||||
HANDLE* const hSignal,
|
||||
PROCESS_INFORMATION* const piPty)
|
||||
{
|
||||
// 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).
|
||||
HANDLE outPipeConhostSide;
|
||||
HANDLE inPipeConhostSide;
|
||||
HANDLE signalPipeConhostSide;
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa = { 0 };
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = FALSE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
|
||||
CreatePipe(&inPipeConhostSide, hInput, &sa, 0);
|
||||
CreatePipe(hOutput, &outPipeConhostSide, &sa, 0);
|
||||
|
||||
// Mark inheritable for signal handle when creating. It'll have the same value on the other side.
|
||||
sa.bInheritHandle = TRUE;
|
||||
CreatePipe(&signalPipeConhostSide, hSignal, &sa, 0);
|
||||
|
||||
SetHandleInformation(inPipeConhostSide, HANDLE_FLAG_INHERIT, 1);
|
||||
SetHandleInformation(outPipeConhostSide, HANDLE_FLAG_INHERIT, 1);
|
||||
|
||||
std::wstring conhostCmdline = L"conhost.exe";
|
||||
conhostCmdline += L" --headless";
|
||||
std::wstringstream ss;
|
||||
if (w != 0 && h != 0)
|
||||
{
|
||||
ss << L" --width " << (unsigned long)w;
|
||||
ss << L" --height " << (unsigned long)h;
|
||||
}
|
||||
|
||||
ss << L" --signal 0x" << std::hex << HandleToUlong(signalPipeConhostSide);
|
||||
conhostCmdline += ss.str();
|
||||
conhostCmdline += L" -- ";
|
||||
conhostCmdline += cmdline;
|
||||
|
||||
STARTUPINFO si = { 0 };
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
si.hStdInput = inPipeConhostSide;
|
||||
si.hStdOutput = outPipeConhostSide;
|
||||
si.hStdError = outPipeConhostSide;
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
|
||||
std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(conhostCmdline.length() + 1);
|
||||
if (mutableCommandline == nullptr)
|
||||
{
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
HRESULT hr = StringCchCopy(mutableCommandline.get(), conhostCmdline.length() + 1, conhostCmdline.c_str());
|
||||
if (!SUCCEEDED(hr))
|
||||
{
|
||||
return hr;
|
||||
}
|
||||
|
||||
bool fSuccess = !!CreateProcessW(
|
||||
nullptr,
|
||||
mutableCommandline.get(),
|
||||
nullptr, // lpProcessAttributes
|
||||
nullptr, // lpThreadAttributes
|
||||
true, // bInheritHandles
|
||||
0, // dwCreationFlags
|
||||
nullptr, // lpEnvironment
|
||||
nullptr, // lpCurrentDirectory
|
||||
&si, // lpStartupInfo
|
||||
piPty // lpProcessInformation
|
||||
);
|
||||
|
||||
CloseHandle(inPipeConhostSide);
|
||||
CloseHandle(outPipeConhostSide);
|
||||
CloseHandle(signalPipeConhostSide);
|
||||
|
||||
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Resizes the pty that's connected to hSignal.
|
||||
// Arguments:
|
||||
// - hSignal: A signal pipe as returned by CreateConPty.
|
||||
// - w: The new width of the pty, in characters
|
||||
// - h: The new height of the pty, in characters
|
||||
// Return Value:
|
||||
// - true if the resize succeeded, else false.
|
||||
__declspec(noinline) inline bool SignalResizeWindow(HANDLE hSignal, const unsigned short w, const unsigned short h)
|
||||
{
|
||||
unsigned short signalPacket[3];
|
||||
signalPacket[0] = PTY_SIGNAL_RESIZE_WINDOW;
|
||||
signalPacket[1] = w;
|
||||
signalPacket[2] = h;
|
||||
|
||||
return !!WriteFile(hSignal, signalPacket, sizeof(signalPacket), nullptr, nullptr);
|
||||
}
|
|
@ -1,402 +0,0 @@
|
|||
// 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(const PipeReadCallback pfnReadCallback,
|
||||
const bool fHeadless,
|
||||
const bool fUseConpty,
|
||||
const COORD 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;
|
||||
|
||||
auto 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;
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- VtConsole.hpp
|
||||
|
||||
Abstract:
|
||||
- This serves as an abstraction to allow for a test connection to a conhost.exe running
|
||||
in VT server mode. It's abstracted to allow multiple simultaneous connections to multiple
|
||||
conhost.exe servers.
|
||||
|
||||
Author(s):
|
||||
- Mike Griese (MiGrie) 2017
|
||||
--*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <wil/result.h>
|
||||
#include <wil/resource.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
typedef void (*PipeReadCallback)(BYTE* buffer, DWORD dwRead);
|
||||
|
||||
class VtConsole
|
||||
{
|
||||
public:
|
||||
VtConsole(const PipeReadCallback pfnReadCallback, const bool fHeadless, const bool fUseConpty, const COORD initialSize);
|
||||
void spawn();
|
||||
void spawn(const std::wstring& command);
|
||||
|
||||
HANDLE inPipe();
|
||||
HANDLE outPipe();
|
||||
|
||||
static const DWORD sInPipeOpenMode = PIPE_ACCESS_DUPLEX;
|
||||
static const DWORD sOutPipeOpenMode = PIPE_ACCESS_INBOUND;
|
||||
|
||||
static const DWORD sInPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT;
|
||||
static const DWORD sOutPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT;
|
||||
|
||||
void activate();
|
||||
void deactivate();
|
||||
|
||||
void signalWindow(unsigned short sx, unsigned short sy);
|
||||
|
||||
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
|
||||
|
||||
bool WriteInput(std::string& seq);
|
||||
|
||||
bool Repaint();
|
||||
bool Resize(const unsigned short rows, const unsigned short cols);
|
||||
|
||||
private:
|
||||
COORD _lastDimensions;
|
||||
|
||||
PROCESS_INFORMATION _piPty;
|
||||
PROCESS_INFORMATION _piClient;
|
||||
|
||||
HANDLE _outPipe = INVALID_HANDLE_VALUE;
|
||||
HANDLE _inPipe = INVALID_HANDLE_VALUE;
|
||||
HANDLE _signalPipe = INVALID_HANDLE_VALUE;
|
||||
|
||||
HPCON _hPC;
|
||||
|
||||
bool _connected = false;
|
||||
bool _active = false;
|
||||
bool _fUseConPty = false;
|
||||
bool _fHeadless = false;
|
||||
|
||||
PipeReadCallback _pfnReadCallback;
|
||||
|
||||
DWORD _dwOutputThreadId = 0;
|
||||
HANDLE _hOutputThread = nullptr;
|
||||
|
||||
void _createPseudoConsole(const std::wstring& command);
|
||||
void _createConptyManually(const std::wstring& command);
|
||||
void _createConptyViaCommandline(const std::wstring& command);
|
||||
|
||||
void _spawn(const std::wstring& command);
|
||||
|
||||
DWORD _OutputThread();
|
||||
};
|
|
@ -11,11 +11,15 @@
|
|||
<Import Project="..\..\common.build.pre.props" />
|
||||
<Import Project="..\..\common.nugetversions.props" />
|
||||
<ItemGroup>
|
||||
<ClInclude Include="VtConsole.hpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="VtConsole.cpp" />
|
||||
<ProjectReference Include="..\..\types\lib\types.vcxproj">
|
||||
<Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\winconpty\lib\winconptylib.vcxproj">
|
||||
<Project>{58a03bb2-df5a-4b66-91a0-7ef3ba01269a}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
|
@ -30,4 +34,4 @@
|
|||
<Import Project="..\..\common.build.post.props" />
|
||||
<Import Project="..\..\common.build.tests.props" />
|
||||
<Import Project="..\..\common.nugetversions.targets" />
|
||||
</Project>
|
||||
</Project>
|
|
@ -19,4 +19,8 @@
|
|||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,605 +1,261 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include <windows.h>
|
||||
#include <wil/Common.h>
|
||||
#include <wil/result.h>
|
||||
#include <wil/resource.h>
|
||||
#include <wil/wistd_functional.h>
|
||||
#include <wil/wistd_memory.h>
|
||||
#include <cstdlib> /* srand, rand */
|
||||
#include <ctime> /* time */
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
#define CONPTY_IMPEXP
|
||||
#include <conpty-static.h>
|
||||
|
||||
#include "VtConsole.hpp"
|
||||
#include <wil/win32_helpers.h>
|
||||
|
||||
using namespace std;
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// "Do Unicode" strings - C-b, u, then one of these characters to emit a string
|
||||
// of characters that aren't really possible to read from the console currently.
|
||||
const int TEST_LANG_NONE = 0; // 0
|
||||
const int TEST_LANG_CYRILLIC = 1; // 1
|
||||
const int TEST_LANG_CHINESE = 2; // 2
|
||||
const int TEST_LANG_JAPANESE = 3; // 3
|
||||
const int TEST_LANG_KOREAN = 4; // 4
|
||||
const int TEST_LANG_GOOD_POUND = 5; // #
|
||||
const int TEST_LANG_BAD_POUND = 6; // $
|
||||
#include <string_view>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// State
|
||||
HANDLE hOut;
|
||||
HANDLE hIn;
|
||||
short lastTerminalWidth;
|
||||
short lastTerminalHeight;
|
||||
#define CONSOLE_READ_NOWAIT 0x0002
|
||||
|
||||
std::deque<VtConsole*> consoles;
|
||||
// A console for printing debug output to
|
||||
VtConsole* debug;
|
||||
BOOL WINAPI ReadConsoleInputExA(
|
||||
_In_ HANDLE hConsoleInput,
|
||||
_Out_writes_(nLength) PINPUT_RECORD lpBuffer,
|
||||
_In_ DWORD nLength,
|
||||
_Out_ LPDWORD lpNumberOfEventsRead,
|
||||
_In_ USHORT wFlags);
|
||||
|
||||
bool prefixPressed = false;
|
||||
bool doUnicode = false;
|
||||
int lang = TEST_LANG_NONE;
|
||||
|
||||
bool g_headless = false;
|
||||
bool g_useConpty = false;
|
||||
bool g_useOutfile = false;
|
||||
std::wstring outfile = L"vtpt.out";
|
||||
HANDLE hOutFile = INVALID_HANDLE_VALUE;
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Forward decls
|
||||
std::string toPrintableString(std::string& inString);
|
||||
void toPrintableBuffer(char c, char* printBuffer, int* printCch);
|
||||
std::string csi(string seq);
|
||||
void PrintInputToDebug(std::string& rawInput);
|
||||
void PrintOutputToDebug(std::string& rawOutput);
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ReadCallback(BYTE* buffer, DWORD dwRead)
|
||||
// Forward declare the bits from types/inc/utils.hpp that we need so we don't need to pull in a dozen STL headers.
|
||||
namespace Microsoft::Console::Utils
|
||||
{
|
||||
// We already set the console to UTF-8 CP, so we can just write straight to it
|
||||
bool fSuccess = !!WriteFile(hOut, buffer, dwRead, nullptr, nullptr);
|
||||
if (fSuccess && g_useOutfile)
|
||||
struct Pipe
|
||||
{
|
||||
fSuccess = !!WriteFile(hOutFile, buffer, dwRead, nullptr, nullptr);
|
||||
}
|
||||
if (fSuccess)
|
||||
{
|
||||
std::string renderData = std::string((char*)buffer, dwRead);
|
||||
PrintOutputToDebug(renderData);
|
||||
}
|
||||
else
|
||||
{
|
||||
HRESULT hr = GetLastError();
|
||||
exit(hr);
|
||||
}
|
||||
wil::unique_hfile server;
|
||||
wil::unique_hfile client;
|
||||
};
|
||||
Pipe CreateOverlappedPipe(DWORD openMode, DWORD bufferSize);
|
||||
}
|
||||
|
||||
void DebugReadCallback(BYTE* /*buffer*/, DWORD /*dwRead*/)
|
||||
static Microsoft::Console::Utils::Pipe pipe;
|
||||
|
||||
static COORD getViewportSize()
|
||||
{
|
||||
// do nothing.
|
||||
CONSOLE_SCREEN_BUFFER_INFOEX csbiex{ .cbSize = sizeof(csbiex) };
|
||||
THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfoEx(GetStdHandle(STD_OUTPUT_HANDLE), &csbiex));
|
||||
const SHORT w = csbiex.dwSize.X;
|
||||
const SHORT h = csbiex.srWindow.Bottom - csbiex.srWindow.Top + 1;
|
||||
return { w, h };
|
||||
}
|
||||
|
||||
VtConsole* getConsole()
|
||||
static int run(int argc, const wchar_t* argv[])
|
||||
{
|
||||
return consoles[0];
|
||||
}
|
||||
const auto inputHandle = GetStdHandle(STD_INPUT_HANDLE);
|
||||
const auto outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
wil::unique_hfile debugOutput;
|
||||
|
||||
void nextConsole()
|
||||
{
|
||||
auto con = consoles[0];
|
||||
con->deactivate();
|
||||
consoles.pop_front();
|
||||
consoles.push_back(con);
|
||||
con = consoles[0];
|
||||
con->activate();
|
||||
// Force the new console to repaint.
|
||||
std::string seq = csi("7t");
|
||||
con->WriteInput(seq);
|
||||
}
|
||||
|
||||
HANDLE inPipe()
|
||||
{
|
||||
return getConsole()->inPipe();
|
||||
}
|
||||
|
||||
HANDLE outPipe()
|
||||
{
|
||||
return getConsole()->outPipe();
|
||||
}
|
||||
|
||||
void newConsole()
|
||||
{
|
||||
auto con = new VtConsole(ReadCallback, g_headless, g_useConpty, { lastTerminalWidth, lastTerminalHeight });
|
||||
con->spawn();
|
||||
consoles.push_back(con);
|
||||
}
|
||||
|
||||
void signalConsole()
|
||||
{
|
||||
// The 0th console is always our active one.
|
||||
// This is a test-only scenario to set the window to 30 wide by 10 tall.
|
||||
consoles.at(0)->signalWindow(30, 10);
|
||||
}
|
||||
|
||||
std::string csi(string seq)
|
||||
{
|
||||
// Note: This doesn't do anything for the debug console currently.
|
||||
// Somewhere, the TTY eats the control sequences. Still useful though.
|
||||
string fullSeq = "\x1b[";
|
||||
fullSeq += seq;
|
||||
return fullSeq;
|
||||
}
|
||||
|
||||
void printKeyEvent(KEY_EVENT_RECORD keyEvent)
|
||||
{
|
||||
// If printable:
|
||||
if (keyEvent.uChar.AsciiChar > ' ' && keyEvent.uChar.AsciiChar != '\x7f')
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
wprintf(L"Down: %d Repeat: %d KeyCode: 0x%x ScanCode: 0x%x Char: %c (0x%x) KeyState: 0x%x\r\n",
|
||||
keyEvent.bKeyDown,
|
||||
keyEvent.wRepeatCount,
|
||||
keyEvent.wVirtualKeyCode,
|
||||
keyEvent.wVirtualScanCode,
|
||||
keyEvent.uChar.AsciiChar,
|
||||
keyEvent.uChar.AsciiChar,
|
||||
keyEvent.dwControlKeyState);
|
||||
}
|
||||
else
|
||||
{
|
||||
wprintf(L"Down: %d Repeat: %d KeyCode: 0x%x ScanCode: 0x%x Char:(0x%x) KeyState: 0x%x\r\n",
|
||||
keyEvent.bKeyDown,
|
||||
keyEvent.wRepeatCount,
|
||||
keyEvent.wVirtualKeyCode,
|
||||
keyEvent.wVirtualScanCode,
|
||||
keyEvent.uChar.AsciiChar,
|
||||
keyEvent.dwControlKeyState);
|
||||
}
|
||||
}
|
||||
|
||||
void toPrintableBuffer(char c, char* printBuffer, int* printCch)
|
||||
{
|
||||
if (c == '\x1b')
|
||||
{
|
||||
printBuffer[0] = '^';
|
||||
printBuffer[1] = '[';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\x03')
|
||||
{
|
||||
printBuffer[0] = '^';
|
||||
printBuffer[1] = 'C';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\x0')
|
||||
{
|
||||
printBuffer[0] = '\\';
|
||||
printBuffer[1] = '0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\r')
|
||||
{
|
||||
printBuffer[0] = '\\';
|
||||
printBuffer[1] = 'r';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
printBuffer[0] = '\\';
|
||||
printBuffer[1] = 'n';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c < '\x20')
|
||||
{
|
||||
printBuffer[0] = '^';
|
||||
printBuffer[1] = c + 0x40;
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\x7f')
|
||||
{
|
||||
printBuffer[0] = '\\';
|
||||
printBuffer[1] = 'x';
|
||||
printBuffer[2] = '7';
|
||||
printBuffer[3] = 'f';
|
||||
*printCch = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
printBuffer[0] = (char)c;
|
||||
*printCch = 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string toPrintableString(std::string& inString)
|
||||
{
|
||||
std::string retval = "";
|
||||
for (size_t i = 0; i < inString.length(); i++)
|
||||
{
|
||||
char c = inString[i];
|
||||
if (c < '\x20')
|
||||
if (wcscmp(argv[i], L"--out") == 0 && (i + 1) < argc)
|
||||
{
|
||||
retval += "^";
|
||||
char actual = (c + 0x40);
|
||||
retval += std::string(1, actual);
|
||||
}
|
||||
else if (c == '\x7f')
|
||||
{
|
||||
retval += "\\x7f";
|
||||
}
|
||||
else if (c == '\x20')
|
||||
{
|
||||
retval += "SPC";
|
||||
debugOutput.reset(CreateFileW(argv[++i], GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
|
||||
THROW_LAST_ERROR_IF(!debugOutput);
|
||||
}
|
||||
else
|
||||
{
|
||||
retval += std::string(1, c);
|
||||
static constexpr auto help =
|
||||
"USAGE:\r\n"
|
||||
" VtPipeTerm [OPTIONS]\r\n"
|
||||
"\r\n"
|
||||
"OPTIONS:\r\n"
|
||||
" -h, --help\r\n"
|
||||
" Display this help message\r\n"
|
||||
" --out <PATH>\r\n"
|
||||
" Dump output to PATH\r\n";
|
||||
|
||||
WriteFile(outputHandle, help, static_cast<DWORD>(strlen(help)), nullptr, nullptr);
|
||||
return wcscmp(argv[i], L"-h") == 0 || wcscmp(argv[i], L"--help") == 0 ? 0 : 1;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void doResize(const unsigned short width, const unsigned short height)
|
||||
{
|
||||
lastTerminalWidth = width;
|
||||
lastTerminalHeight = height;
|
||||
static const auto pReadConsoleInputExA = GetProcAddressByFunctionDeclaration(GetModuleHandleW(L"kernel32.dll"), ReadConsoleInputExA);
|
||||
THROW_LAST_ERROR_IF_NULL(pReadConsoleInputExA);
|
||||
|
||||
for (auto console : consoles)
|
||||
pipe = Microsoft::Console::Utils::CreateOverlappedPipe(PIPE_ACCESS_DUPLEX, 128 * 1024);
|
||||
|
||||
auto viewportSize = getViewportSize();
|
||||
|
||||
HPCON hPC = nullptr;
|
||||
THROW_IF_FAILED(ConptyCreatePseudoConsole(viewportSize, pipe.client.get(), pipe.client.get(), 0, &hPC));
|
||||
pipe.client.reset();
|
||||
|
||||
PROCESS_INFORMATION pi;
|
||||
{
|
||||
console->Resize(height, width);
|
||||
wchar_t commandLine[MAX_PATH] = LR"(C:\Windows\System32\cmd.exe)";
|
||||
|
||||
STARTUPINFOEX siEx{};
|
||||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEX);
|
||||
siEx.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
|
||||
|
||||
char attrList[128];
|
||||
SIZE_T size = sizeof(attrList);
|
||||
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(&attrList[0]);
|
||||
THROW_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size));
|
||||
THROW_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(siEx.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC, sizeof(HPCON), nullptr, nullptr));
|
||||
|
||||
THROW_IF_WIN32_BOOL_FALSE(CreateProcessW(
|
||||
/* lpApplicationName */ nullptr,
|
||||
/* lpCommandLine */ commandLine,
|
||||
/* lpProcessAttributes */ nullptr,
|
||||
/* lpThreadAttributes */ nullptr,
|
||||
/* bInheritHandles */ false,
|
||||
/* dwCreationFlags */ EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT,
|
||||
/* lpEnvironment */ nullptr,
|
||||
/* lpCurrentDirectory */ nullptr,
|
||||
/* lpStartupInfo */ &siEx.StartupInfo,
|
||||
/* lpProcessInformation */ &pi));
|
||||
}
|
||||
}
|
||||
|
||||
void handleResize()
|
||||
{
|
||||
CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
|
||||
csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
|
||||
bool fSuccess = !!GetConsoleScreenBufferInfoEx(hOut, &csbiex);
|
||||
if (fSuccess)
|
||||
{
|
||||
SMALL_RECT srViewport = csbiex.srWindow;
|
||||
THROW_IF_FAILED(ConptyReleasePseudoConsole(hPC));
|
||||
|
||||
unsigned short width = srViewport.Right - srViewport.Left + 1;
|
||||
unsigned short height = srViewport.Bottom - srViewport.Top + 1;
|
||||
|
||||
doResize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void handleManyEvents(const INPUT_RECORD* const inputBuffer, int cEvents)
|
||||
{
|
||||
char* const buffer = new char[cEvents];
|
||||
char* const printableBuffer = new char[cEvents * 4];
|
||||
memset(buffer, 0, cEvents);
|
||||
memset(printableBuffer, 0, cEvents * 4);
|
||||
|
||||
char* nextBuffer = buffer;
|
||||
char* nextPrintable = printableBuffer;
|
||||
int bufferCch = 0;
|
||||
int printableCch = 0;
|
||||
|
||||
for (int i = 0; i < cEvents; ++i)
|
||||
{
|
||||
INPUT_RECORD event = inputBuffer[i];
|
||||
|
||||
if (event.EventType == KEY_EVENT)
|
||||
{
|
||||
KEY_EVENT_RECORD keyEvent = event.Event.KeyEvent;
|
||||
if (keyEvent.bKeyDown)
|
||||
// Forward Ctrl-C to the PTY.
|
||||
SetConsoleCtrlHandler(
|
||||
[](DWORD type) -> BOOL {
|
||||
switch (type)
|
||||
{
|
||||
const char c = keyEvent.uChar.AsciiChar;
|
||||
|
||||
if (c == '\0' && keyEvent.wVirtualScanCode != 0)
|
||||
{
|
||||
// This is a special keyboard key that was pressed, not actually NUL
|
||||
continue;
|
||||
}
|
||||
if (doUnicode)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '1':
|
||||
lang = TEST_LANG_CYRILLIC;
|
||||
break;
|
||||
case '2':
|
||||
lang = TEST_LANG_CHINESE;
|
||||
break;
|
||||
case '3':
|
||||
lang = TEST_LANG_JAPANESE;
|
||||
break;
|
||||
case '4':
|
||||
lang = TEST_LANG_KOREAN;
|
||||
break;
|
||||
case '#':
|
||||
lang = TEST_LANG_GOOD_POUND;
|
||||
break;
|
||||
case '$':
|
||||
lang = TEST_LANG_BAD_POUND;
|
||||
break;
|
||||
default:
|
||||
doUnicode = false;
|
||||
lang = TEST_LANG_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!prefixPressed)
|
||||
{
|
||||
if (c == '\x2')
|
||||
{
|
||||
prefixPressed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
*nextBuffer = c;
|
||||
nextBuffer++;
|
||||
bufferCch++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'n':
|
||||
case '\t':
|
||||
nextConsole();
|
||||
break;
|
||||
case 't':
|
||||
newConsole();
|
||||
nextConsole();
|
||||
break;
|
||||
case 'u':
|
||||
doUnicode = true;
|
||||
break;
|
||||
case 'r':
|
||||
signalConsole();
|
||||
break;
|
||||
default:
|
||||
*nextBuffer = c;
|
||||
nextBuffer++;
|
||||
bufferCch++;
|
||||
}
|
||||
prefixPressed = false;
|
||||
}
|
||||
int numPrintable = 0;
|
||||
toPrintableBuffer(c, nextPrintable, &numPrintable);
|
||||
nextPrintable += numPrintable;
|
||||
printableCch += numPrintable;
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_BREAK_EVENT:
|
||||
WriteFile(pipe.server.get(), "\x03", 1, nullptr, nullptr);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (event.EventType == WINDOW_BUFFER_SIZE_EVENT)
|
||||
{
|
||||
WINDOW_BUFFER_SIZE_RECORD resize = event.Event.WindowBufferSizeEvent;
|
||||
handleResize();
|
||||
}
|
||||
}
|
||||
},
|
||||
TRUE);
|
||||
|
||||
if (bufferCch > 0)
|
||||
{
|
||||
std::string vtseq = std::string(buffer, bufferCch);
|
||||
std::string printSeq = std::string(printableBuffer, printableCch);
|
||||
OVERLAPPED outputConptyOverlapped{ .hEvent = CreateEventW(nullptr, TRUE, TRUE, nullptr) };
|
||||
HANDLE handles[] = { inputHandle, outputConptyOverlapped.hEvent };
|
||||
INPUT_RECORD inputRecords[4096];
|
||||
char inputConptyBuffer[ARRAYSIZE(inputRecords)];
|
||||
char outputConptyBuffer[256 * 1024];
|
||||
|
||||
getConsole()->WriteInput(vtseq);
|
||||
PrintInputToDebug(vtseq);
|
||||
}
|
||||
if (doUnicode && lang != TEST_LANG_NONE)
|
||||
{
|
||||
std::string str;
|
||||
switch (lang)
|
||||
{
|
||||
case TEST_LANG_CYRILLIC:
|
||||
str = "Лорем ипсум долор сит амет, пер цлита поссит ех, ат мунере фабулас петентиум сит.";
|
||||
break;
|
||||
case TEST_LANG_CHINESE:
|
||||
str = "側経意責家方家閉討店暖育田庁載社転線宇。";
|
||||
break;
|
||||
case TEST_LANG_JAPANESE:
|
||||
str = "旅ロ京青利セムレ弱改フヨス波府かばぼ意送でぼ調掲察たス日西重ケアナ住橋ユムミク順待ふかんぼ人奨貯鏡すびそ。";
|
||||
break;
|
||||
case TEST_LANG_KOREAN:
|
||||
str = "국민경제의 발전을 위한 중요정책의 수립에 관하여 대통령의 자문에 응하기 위하여 국민경제자문회의를 둘 수 있다.";
|
||||
break;
|
||||
case TEST_LANG_GOOD_POUND:
|
||||
str = "\xc2\xa3"; // UTF-8 £
|
||||
break;
|
||||
case TEST_LANG_BAD_POUND:
|
||||
str = "\xa3"; // UTF-16 £
|
||||
break;
|
||||
default:
|
||||
str = "";
|
||||
break;
|
||||
}
|
||||
getConsole()->WriteInput(str);
|
||||
PrintInputToDebug(str);
|
||||
|
||||
doUnicode = false;
|
||||
lang = TEST_LANG_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintInputToDebug(std::string& rawInput)
|
||||
{
|
||||
if (debug != nullptr)
|
||||
{
|
||||
std::string printable = toPrintableString(rawInput);
|
||||
std::stringstream ss;
|
||||
ss << "Input \"" << printable << "\" [" << rawInput.length() << "]\n";
|
||||
std::string output = ss.str();
|
||||
debug->WriteInput(output);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintOutputToDebug(std::string& rawOutput)
|
||||
{
|
||||
if (debug != nullptr)
|
||||
{
|
||||
std::string printable = toPrintableString(rawOutput);
|
||||
std::stringstream ss;
|
||||
ss << printable << "\n";
|
||||
std::string output = ss.str();
|
||||
debug->WriteInput(output);
|
||||
}
|
||||
}
|
||||
|
||||
void SetupOutput()
|
||||
{
|
||||
DWORD dwMode = 0;
|
||||
THROW_LAST_ERROR_IF(!GetConsoleMode(hOut, &dwMode));
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
dwMode |= DISABLE_NEWLINE_AUTO_RETURN;
|
||||
THROW_LAST_ERROR_IF(!SetConsoleMode(hOut, dwMode));
|
||||
}
|
||||
void SetupInput()
|
||||
{
|
||||
DWORD dwInMode = 0;
|
||||
GetConsoleMode(hIn, &dwInMode);
|
||||
dwInMode = ENABLE_VIRTUAL_TERMINAL_INPUT;
|
||||
SetConsoleMode(hIn, dwInMode);
|
||||
}
|
||||
|
||||
DWORD WINAPI InputThread(LPVOID /*lpParameter*/)
|
||||
{
|
||||
// Because the input thread ends up owning the lifetime of the application,
|
||||
// Set/restore the CP here.
|
||||
|
||||
unsigned int launchOutputCP = GetConsoleOutputCP();
|
||||
unsigned int launchCP = GetConsoleCP();
|
||||
THROW_LAST_ERROR_IF(!SetConsoleOutputCP(CP_UTF8));
|
||||
THROW_LAST_ERROR_IF(!SetConsoleCP(CP_UTF8));
|
||||
auto restore = wil::scope_exit([&] {
|
||||
SetConsoleOutputCP(launchOutputCP);
|
||||
SetConsoleCP(launchCP);
|
||||
});
|
||||
// Kickstart the overlapped read of the pipe and cause the
|
||||
// outputConptyOverlapped members to be filled up appropriately.
|
||||
ReadFile(pipe.server.get(), &outputConptyBuffer[0], sizeof(outputConptyBuffer), nullptr, &outputConptyOverlapped);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
INPUT_RECORD rc[256];
|
||||
DWORD dwRead = 0;
|
||||
// Not to future self: You can't read utf-8 from the console yet.
|
||||
bool fSuccess = !!ReadConsoleInput(hIn, rc, 256, &dwRead);
|
||||
if (fSuccess)
|
||||
switch (WaitForMultipleObjectsEx(ARRAYSIZE(handles), &handles[0], FALSE, INFINITE, FALSE))
|
||||
{
|
||||
handleManyEvents(rc, dwRead);
|
||||
case WAIT_OBJECT_0 + 0:
|
||||
{
|
||||
DWORD read;
|
||||
if (!pReadConsoleInputExA(inputHandle, &inputRecords[0], ARRAYSIZE(inputRecords), &read, CONSOLE_READ_NOWAIT) || read == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD write = 0;
|
||||
bool resize = false;
|
||||
|
||||
for (DWORD i = 0; i < read; ++i)
|
||||
{
|
||||
switch (inputRecords[i].EventType)
|
||||
{
|
||||
case KEY_EVENT:
|
||||
if (inputRecords[i].Event.KeyEvent.bKeyDown)
|
||||
{
|
||||
inputConptyBuffer[write++] = inputRecords[i].Event.KeyEvent.uChar.AsciiChar;
|
||||
}
|
||||
break;
|
||||
case WINDOW_BUFFER_SIZE_EVENT:
|
||||
resize = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (resize)
|
||||
{
|
||||
const auto size = getViewportSize();
|
||||
if (memcmp(&viewportSize, &size, sizeof(COORD)) != 0)
|
||||
{
|
||||
viewportSize = size;
|
||||
THROW_IF_FAILED(ConptyResizePseudoConsole(hPC, viewportSize));
|
||||
}
|
||||
}
|
||||
|
||||
if (write != 0)
|
||||
{
|
||||
DWORD written;
|
||||
if (!WriteFile(pipe.server.get(), &inputConptyBuffer[0], write, &written, nullptr) || written != read)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
{
|
||||
exit(GetLastError());
|
||||
DWORD read;
|
||||
if (!GetOverlappedResult(pipe.server.get(), &outputConptyOverlapped, &read, FALSE))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
DWORD written;
|
||||
if (debugOutput)
|
||||
{
|
||||
WriteFile(debugOutput.get(), &outputConptyBuffer[0], read, &written, nullptr);
|
||||
}
|
||||
if (!WriteFile(outputHandle, &outputConptyBuffer[0], read, &written, nullptr) || written != read)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
} while (ReadFile(pipe.server.get(), &outputConptyBuffer[0], sizeof(outputConptyBuffer), &read, &outputConptyOverlapped));
|
||||
|
||||
if (GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateIOThreads()
|
||||
int wmain(int argc, const wchar_t* argv[])
|
||||
{
|
||||
// The VtConsoles themselves handle their output threads.
|
||||
const auto inputHandle = GetStdHandle(STD_INPUT_HANDLE);
|
||||
const auto outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
DWORD dwInputThreadId = (DWORD)-1;
|
||||
HANDLE hInputThread = CreateThread(nullptr,
|
||||
0,
|
||||
InputThread,
|
||||
nullptr,
|
||||
0,
|
||||
&dwInputThreadId);
|
||||
hInputThread;
|
||||
const auto previousInputCP = GetConsoleCP();
|
||||
const auto previousOutputCP = GetConsoleOutputCP();
|
||||
DWORD previousInputMode = 0;
|
||||
DWORD previousOutputMode = 0;
|
||||
GetConsoleMode(inputHandle, &previousInputMode);
|
||||
GetConsoleMode(outputHandle, &previousOutputMode);
|
||||
|
||||
SetConsoleCP(CP_UTF8);
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
SetConsoleMode(inputHandle, ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT | ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_VIRTUAL_TERMINAL_INPUT);
|
||||
SetConsoleMode(outputHandle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);
|
||||
|
||||
int exitCode = 0;
|
||||
|
||||
try
|
||||
{
|
||||
exitCode = run(argc, argv);
|
||||
}
|
||||
catch (const wil::ResultException& e)
|
||||
{
|
||||
printf("Error: %s\n", e.what());
|
||||
exitCode = e.GetErrorCode();
|
||||
}
|
||||
|
||||
SetConsoleMode(outputHandle, previousOutputMode);
|
||||
SetConsoleCP(previousInputCP);
|
||||
SetConsoleOutputCP(previousOutputCP);
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
|
||||
{
|
||||
switch (fdwCtrlType)
|
||||
{
|
||||
// Handle the CTRL-C signal.
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_BREAK_EVENT:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// this function has unreachable code due to its unusual lifetime. We
|
||||
// disable the warning about it here.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4702)
|
||||
int __cdecl wmain(int argc, WCHAR* argv[])
|
||||
{
|
||||
// initialize random seed:
|
||||
srand((unsigned int)time(nullptr));
|
||||
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
||||
|
||||
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
hIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
|
||||
bool fUseDebug = false;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
for (int i = 0; i < argc; ++i)
|
||||
{
|
||||
std::wstring arg = argv[i];
|
||||
if (arg == std::wstring(L"--headless"))
|
||||
{
|
||||
g_headless = true;
|
||||
}
|
||||
if (arg == std::wstring(L"--conpty"))
|
||||
{
|
||||
g_useConpty = true;
|
||||
}
|
||||
else if (arg == std::wstring(L"--debug"))
|
||||
{
|
||||
fUseDebug = true;
|
||||
}
|
||||
else if (arg == std::wstring(L"--out") && i + 1 < argc)
|
||||
{
|
||||
g_useOutfile = true;
|
||||
outfile = argv[i + 1];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (g_useConpty)
|
||||
{
|
||||
printf("Launching vtpipeterm with conpty API...\n");
|
||||
Sleep(1000);
|
||||
}
|
||||
|
||||
if (g_useOutfile)
|
||||
{
|
||||
hOutFile = CreateFileW(outfile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (hOutFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
printf("Failed to open outfile (%ls) for writing\n", outfile.c_str());
|
||||
Sleep(1000);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
SetupOutput();
|
||||
SetupInput();
|
||||
|
||||
// handleResize will get our initial terminal dimensions.
|
||||
handleResize();
|
||||
|
||||
newConsole();
|
||||
getConsole()->activate();
|
||||
CreateIOThreads();
|
||||
|
||||
if (fUseDebug)
|
||||
{
|
||||
// Create a debug console for writing debugging output to.
|
||||
debug = new VtConsole(DebugReadCallback, false, false, { 80, 32 });
|
||||
// Echo stdin to stdout, but ignore newlines (so cat doesn't echo the input)
|
||||
// debug->spawn(L"ubuntu run tr -d '\n' | cat -sA");
|
||||
debug->spawn(L"wsl tr -d '\n' | cat -sA");
|
||||
debug->activate();
|
||||
}
|
||||
|
||||
// Exit the thread so the CRT won't clean us up and kill. The IO thread owns the lifetime now.
|
||||
ExitThread(S_OK);
|
||||
// We won't hit this. The ExitThread above will kill the caller at this point.
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include <windows.h>
|
||||
#include <ntverp.h>
|
||||
|
||||
#define VER_FILETYPE VFT_APP
|
||||
#define VER_FILESUBTYPE VFT_UNKNOWN
|
||||
#define VER_FILEDESCRIPTION_STR "Acts as a terminal for connecting to vtio conhosts and render to the current console window."
|
||||
#define VER_INTERNALNAME_STR "vtpipeterm"
|
||||
#define VER_ORIGINALFILENAME_STR "vtpipeterm.EXE"
|
||||
|
||||
|
||||
#include <common.ver>
|
||||
|
||||
|
|
@ -22,8 +22,6 @@ TARGETLIBS=\
|
|||
$(ONECORE_EXTERNAL_SDK_LIB_VPATH_L)\onecore.lib
|
||||
|
||||
SOURCES=main.cpp \
|
||||
VtConsole.cpp \
|
||||
res.rc \
|
||||
|
||||
TARGET_DESTINATION=retail
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче