X-Git-Url: https://git.josue.xyz/?p=VSoRC%2F.git;a=blobdiff_plain;f=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Fagent%2FAgent.cc;fp=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Fagent%2FAgent.cc;h=0000000000000000000000000000000000000000;hp=a77b6442c6d98a9ad45509c0b95aa2b70e46551a;hb=5e96dd57ddd883604e87f62bdddcb111c63a6e1a;hpb=acb5f682a2b75b972710cabd81658f63071324b0 diff --git a/node_modules/node-pty/deps/winpty/src/agent/Agent.cc b/node_modules/node-pty/deps/winpty/src/agent/Agent.cc deleted file mode 100644 index a77b644..0000000 --- a/node_modules/node-pty/deps/winpty/src/agent/Agent.cc +++ /dev/null @@ -1,605 +0,0 @@ -// Copyright (c) 2011-2015 Ryan Prichard -// -// 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. - -#include "Agent.h" - -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include "../include/winpty_constants.h" - -#include "../shared/AgentMsg.h" -#include "../shared/Buffer.h" -#include "../shared/DebugClient.h" -#include "../shared/GenRandom.h" -#include "../shared/StringBuilder.h" -#include "../shared/StringUtil.h" -#include "../shared/WindowsVersion.h" -#include "../shared/WinptyAssert.h" - -#include "ConsoleFont.h" -#include "ConsoleInput.h" -#include "NamedPipe.h" -#include "Scraper.h" -#include "Terminal.h" -#include "Win32ConsoleBuffer.h" - -namespace { - -static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) -{ - if (dwCtrlType == CTRL_C_EVENT) { - // Do nothing and claim to have handled the event. - return TRUE; - } - return FALSE; -} - -// We can detect the new Windows 10 console by observing the effect of the -// Mark command. In older consoles, Mark temporarily moves the cursor to the -// top-left of the console window. In the new console, the cursor isn't -// initially moved. -// -// We might like to use Mark to freeze the console, but we can't, because when -// the Mark command ends, the console moves the cursor back to its starting -// point, even if the console application has moved it in the meantime. -static void detectNewWindows10Console( - Win32Console &console, Win32ConsoleBuffer &buffer) -{ - if (!isAtLeastWindows8()) { - return; - } - - ConsoleScreenBufferInfo info = buffer.bufferInfo(); - - // Make sure the window isn't 1x1. AFAIK, this should never happen - // accidentally. It is difficult to make it happen deliberately. - if (info.srWindow.Left == info.srWindow.Right && - info.srWindow.Top == info.srWindow.Bottom) { - trace("detectNewWindows10Console: Initial console window was 1x1 -- " - "expanding for test"); - setSmallFont(buffer.conout(), 400, false); - buffer.moveWindow(SmallRect(0, 0, 1, 1)); - buffer.resizeBuffer(Coord(400, 1)); - buffer.moveWindow(SmallRect(0, 0, 2, 1)); - // This use of GetLargestConsoleWindowSize ought to be unnecessary - // given the behavior I've seen from moveWindow(0, 0, 1, 1), but - // I'd like to be especially sure, considering that this code will - // rarely be tested. - const auto largest = GetLargestConsoleWindowSize(buffer.conout()); - buffer.moveWindow( - SmallRect(0, 0, std::min(largest.X, buffer.bufferSize().X), 1)); - info = buffer.bufferInfo(); - ASSERT(info.srWindow.Right > info.srWindow.Left && - "Could not expand console window from 1x1"); - } - - // Test whether MARK moves the cursor. - const Coord initialPosition(info.srWindow.Right, info.srWindow.Bottom); - buffer.setCursorPosition(initialPosition); - ASSERT(!console.frozen()); - console.setFreezeUsesMark(true); - console.setFrozen(true); - const bool isNewW10 = (buffer.cursorPosition() == initialPosition); - console.setFrozen(false); - buffer.setCursorPosition(Coord(0, 0)); - - trace("Attempting to detect new Windows 10 console using MARK: %s", - isNewW10 ? "detected" : "not detected"); - console.setFreezeUsesMark(false); - console.setNewW10(isNewW10); -} - -static inline WriteBuffer newPacket() { - WriteBuffer packet; - packet.putRawValue(0); // Reserve space for size. - return packet; -} - -static HANDLE duplicateHandle(HANDLE h) { - HANDLE ret = nullptr; - if (!DuplicateHandle( - GetCurrentProcess(), h, - GetCurrentProcess(), &ret, - 0, FALSE, DUPLICATE_SAME_ACCESS)) { - ASSERT(false && "DuplicateHandle failed!"); - } - return ret; -} - -// It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it -// back to 64-bits. See the MSDN article, "Interprocess Communication Between -// 32-bit and 64-bit Applications". -// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx -static int64_t int64FromHandle(HANDLE h) { - return static_cast(reinterpret_cast(h)); -} - -} // anonymous namespace - -Agent::Agent(LPCWSTR controlPipeName, - uint64_t agentFlags, - int mouseMode, - int initialCols, - int initialRows) : - m_useConerr((agentFlags & WINPTY_FLAG_CONERR) != 0), - m_plainMode((agentFlags & WINPTY_FLAG_PLAIN_OUTPUT) != 0), - m_mouseMode(mouseMode) -{ - trace("Agent::Agent entered"); - - ASSERT(initialCols >= 1 && initialRows >= 1); - initialCols = std::min(initialCols, MAX_CONSOLE_WIDTH); - initialRows = std::min(initialRows, MAX_CONSOLE_HEIGHT); - - const bool outputColor = - !m_plainMode || (agentFlags & WINPTY_FLAG_COLOR_ESCAPES); - const Coord initialSize(initialCols, initialRows); - - auto primaryBuffer = openPrimaryBuffer(); - if (m_useConerr) { - m_errorBuffer = Win32ConsoleBuffer::createErrorBuffer(); - } - - detectNewWindows10Console(m_console, *primaryBuffer); - - m_controlPipe = &connectToControlPipe(controlPipeName); - m_coninPipe = &createDataServerPipe(false, L"conin"); - m_conoutPipe = &createDataServerPipe(true, L"conout"); - if (m_useConerr) { - m_conerrPipe = &createDataServerPipe(true, L"conerr"); - } - - // Send an initial response packet to winpty.dll containing pipe names. - { - auto setupPacket = newPacket(); - setupPacket.putWString(m_coninPipe->name()); - setupPacket.putWString(m_conoutPipe->name()); - if (m_useConerr) { - setupPacket.putWString(m_conerrPipe->name()); - } - writePacket(setupPacket); - } - - std::unique_ptr primaryTerminal; - primaryTerminal.reset(new Terminal(*m_conoutPipe, - m_plainMode, - outputColor)); - m_primaryScraper.reset(new Scraper(m_console, - *primaryBuffer, - std::move(primaryTerminal), - initialSize)); - if (m_useConerr) { - std::unique_ptr errorTerminal; - errorTerminal.reset(new Terminal(*m_conerrPipe, - m_plainMode, - outputColor)); - m_errorScraper.reset(new Scraper(m_console, - *m_errorBuffer, - std::move(errorTerminal), - initialSize)); - } - - m_console.setTitle(m_currentTitle); - - const HANDLE conin = GetStdHandle(STD_INPUT_HANDLE); - m_consoleInput.reset( - new ConsoleInput(conin, m_mouseMode, *this, m_console)); - - // Setup Ctrl-C handling. First restore default handling of Ctrl-C. This - // attribute is inherited by child processes. Then register a custom - // Ctrl-C handler that does nothing. The handler will be called when the - // agent calls GenerateConsoleCtrlEvent. - SetConsoleCtrlHandler(NULL, FALSE); - SetConsoleCtrlHandler(consoleCtrlHandler, TRUE); - - setPollInterval(25); -} - -Agent::~Agent() -{ - trace("Agent::~Agent entered"); - agentShutdown(); - if (m_childProcess != NULL) { - CloseHandle(m_childProcess); - } -} - -// Write a "Device Status Report" command to the terminal. The terminal will -// reply with a row+col escape sequence. Presumably, the DSR reply will not -// split a keypress escape sequence, so it should be safe to assume that the -// bytes before it are complete keypresses. -void Agent::sendDsr() -{ - if (!m_plainMode && !m_conoutPipe->isClosed()) { - m_conoutPipe->write("\x1B[6n"); - } -} - -NamedPipe &Agent::connectToControlPipe(LPCWSTR pipeName) -{ - NamedPipe &pipe = createNamedPipe(); - pipe.connectToServer(pipeName, NamedPipe::OpenMode::Duplex); - pipe.setReadBufferSize(64 * 1024); - return pipe; -} - -// Returns a new server named pipe. It has not yet been connected. -NamedPipe &Agent::createDataServerPipe(bool write, const wchar_t *kind) -{ - const auto name = - (WStringBuilder(128) - << L"\\\\.\\pipe\\winpty-" - << kind << L'-' - << GenRandom().uniqueName()).str_moved(); - NamedPipe &pipe = createNamedPipe(); - pipe.openServerPipe( - name.c_str(), - write ? NamedPipe::OpenMode::Writing - : NamedPipe::OpenMode::Reading, - write ? 8192 : 0, - write ? 0 : 256); - if (!write) { - pipe.setReadBufferSize(64 * 1024); - } - return pipe; -} - -void Agent::onPipeIo(NamedPipe &namedPipe) -{ - if (&namedPipe == m_conoutPipe || &namedPipe == m_conerrPipe) { - autoClosePipesForShutdown(); - } else if (&namedPipe == m_coninPipe) { - pollConinPipe(); - } else if (&namedPipe == m_controlPipe) { - pollControlPipe(); - } -} - -void Agent::pollControlPipe() -{ - if (m_controlPipe->isClosed()) { - trace("Agent exiting (control pipe is closed)"); - shutdown(); - return; - } - - while (true) { - uint64_t packetSize = 0; - const auto amt1 = - m_controlPipe->peek(&packetSize, sizeof(packetSize)); - if (amt1 < sizeof(packetSize)) { - break; - } - ASSERT(packetSize >= sizeof(packetSize) && packetSize <= SIZE_MAX); - if (m_controlPipe->bytesAvailable() < packetSize) { - if (m_controlPipe->readBufferSize() < packetSize) { - m_controlPipe->setReadBufferSize(packetSize); - } - break; - } - std::vector packetData; - packetData.resize(packetSize); - const auto amt2 = m_controlPipe->read(packetData.data(), packetSize); - ASSERT(amt2 == packetSize); - try { - ReadBuffer buffer(std::move(packetData)); - buffer.getRawValue(); // Discard the size. - handlePacket(buffer); - } catch (const ReadBuffer::DecodeError&) { - ASSERT(false && "Decode error"); - } - } -} - -void Agent::handlePacket(ReadBuffer &packet) -{ - const int type = packet.getInt32(); - switch (type) { - case AgentMsg::StartProcess: - handleStartProcessPacket(packet); - break; - case AgentMsg::SetSize: - // TODO: I think it might make sense to collapse consecutive SetSize - // messages. i.e. The terminal process can probably generate SetSize - // messages faster than they can be processed, and some GUIs might - // generate a flood of them, so if we can read multiple SetSize packets - // at once, we can ignore the early ones. - handleSetSizePacket(packet); - break; - case AgentMsg::GetConsoleProcessList: - handleGetConsoleProcessListPacket(packet); - break; - default: - trace("Unrecognized message, id:%d", type); - } -} - -void Agent::writePacket(WriteBuffer &packet) -{ - const auto &bytes = packet.buf(); - packet.replaceRawValue(0, bytes.size()); - m_controlPipe->write(bytes.data(), bytes.size()); -} - -void Agent::handleStartProcessPacket(ReadBuffer &packet) -{ - ASSERT(m_childProcess == nullptr); - ASSERT(!m_closingOutputPipes); - - const uint64_t spawnFlags = packet.getInt64(); - const bool wantProcessHandle = packet.getInt32() != 0; - const bool wantThreadHandle = packet.getInt32() != 0; - const auto program = packet.getWString(); - const auto cmdline = packet.getWString(); - const auto cwd = packet.getWString(); - const auto env = packet.getWString(); - const auto desktop = packet.getWString(); - packet.assertEof(); - - auto cmdlineV = vectorWithNulFromString(cmdline); - auto desktopV = vectorWithNulFromString(desktop); - auto envV = vectorFromString(env); - - LPCWSTR programArg = program.empty() ? nullptr : program.c_str(); - LPWSTR cmdlineArg = cmdline.empty() ? nullptr : cmdlineV.data(); - LPCWSTR cwdArg = cwd.empty() ? nullptr : cwd.c_str(); - LPWSTR envArg = env.empty() ? nullptr : envV.data(); - - STARTUPINFOW sui = {}; - PROCESS_INFORMATION pi = {}; - sui.cb = sizeof(sui); - sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data(); - BOOL inheritHandles = FALSE; - if (m_useConerr) { - inheritHandles = TRUE; - sui.dwFlags |= STARTF_USESTDHANDLES; - sui.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - sui.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); - sui.hStdError = m_errorBuffer->conout(); - } - - const BOOL success = - CreateProcessW(programArg, cmdlineArg, nullptr, nullptr, - /*bInheritHandles=*/inheritHandles, - /*dwCreationFlags=*/CREATE_UNICODE_ENVIRONMENT, - envArg, cwdArg, &sui, &pi); - const int lastError = success ? 0 : GetLastError(); - - trace("CreateProcess: %s %u", - (success ? "success" : "fail"), - static_cast(pi.dwProcessId)); - - auto reply = newPacket(); - if (success) { - int64_t replyProcess = 0; - int64_t replyThread = 0; - if (wantProcessHandle) { - replyProcess = int64FromHandle(duplicateHandle(pi.hProcess)); - } - if (wantThreadHandle) { - replyThread = int64FromHandle(duplicateHandle(pi.hThread)); - } - CloseHandle(pi.hThread); - m_childProcess = pi.hProcess; - m_autoShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN) != 0; - m_exitAfterShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN) != 0; - reply.putInt32(static_cast(StartProcessResult::ProcessCreated)); - reply.putInt64(replyProcess); - reply.putInt64(replyThread); - } else { - reply.putInt32(static_cast(StartProcessResult::CreateProcessFailed)); - reply.putInt32(lastError); - } - writePacket(reply); -} - -void Agent::handleSetSizePacket(ReadBuffer &packet) -{ - const int cols = packet.getInt32(); - const int rows = packet.getInt32(); - packet.assertEof(); - resizeWindow(cols, rows); - auto reply = newPacket(); - writePacket(reply); -} - -void Agent::handleGetConsoleProcessListPacket(ReadBuffer &packet) -{ - packet.assertEof(); - - auto processList = std::vector(64); - auto processCount = GetConsoleProcessList(&processList[0], processList.size()); - if (processList.size() < processCount) { - processList.resize(processCount); - processCount = GetConsoleProcessList(&processList[0], processList.size()); - } - - if (processCount == 0) { - trace("GetConsoleProcessList failed"); - } - - auto reply = newPacket(); - reply.putInt32(processCount); - for (DWORD i = 0; i < processCount; i++) { - reply.putInt32(processList[i]); - } - writePacket(reply); -} - -void Agent::pollConinPipe() -{ - const std::string newData = m_coninPipe->readAllToString(); - if (hasDebugFlag("input_separated_bytes")) { - // This debug flag is intended to help with testing incomplete escape - // sequences and multibyte UTF-8 encodings. (I wonder if the normal - // code path ought to advance a state machine one byte at a time.) - for (size_t i = 0; i < newData.size(); ++i) { - m_consoleInput->writeInput(newData.substr(i, 1)); - } - } else { - m_consoleInput->writeInput(newData); - } -} - -void Agent::onPollTimeout() -{ - m_consoleInput->updateInputFlags(); - const bool enableMouseMode = m_consoleInput->shouldActivateTerminalMouse(); - - // Give the ConsoleInput object a chance to flush input from an incomplete - // escape sequence (e.g. pressing ESC). - m_consoleInput->flushIncompleteEscapeCode(); - - const bool shouldScrapeContent = !m_closingOutputPipes; - - // Check if the child process has exited. - if (m_autoShutdown && - m_childProcess != nullptr && - WaitForSingleObject(m_childProcess, 0) == WAIT_OBJECT_0) { - CloseHandle(m_childProcess); - m_childProcess = nullptr; - - // Close the data socket to signal to the client that the child - // process has exited. If there's any data left to send, send it - // before closing the socket. - m_closingOutputPipes = true; - } - - // Scrape for output *after* the above exit-check to ensure that we collect - // the child process's final output. - if (shouldScrapeContent) { - syncConsoleTitle(); - scrapeBuffers(); - } - - // We must ensure that we disable mouse mode before closing the CONOUT - // pipe, so update the mouse mode here. - m_primaryScraper->terminal().enableMouseMode( - enableMouseMode && !m_closingOutputPipes); - - autoClosePipesForShutdown(); -} - -void Agent::autoClosePipesForShutdown() -{ - if (m_closingOutputPipes) { - // We don't want to close a pipe before it's connected! If we do, the - // libwinpty client may try to connect to a non-existent pipe. This - // case is important for short-lived programs. - if (m_conoutPipe->isConnected() && - m_conoutPipe->bytesToSend() == 0) { - trace("Closing CONOUT pipe (auto-shutdown)"); - m_conoutPipe->closePipe(); - } - if (m_conerrPipe != nullptr && - m_conerrPipe->isConnected() && - m_conerrPipe->bytesToSend() == 0) { - trace("Closing CONERR pipe (auto-shutdown)"); - m_conerrPipe->closePipe(); - } - if (m_exitAfterShutdown && - m_conoutPipe->isClosed() && - (m_conerrPipe == nullptr || m_conerrPipe->isClosed())) { - trace("Agent exiting (exit-after-shutdown)"); - shutdown(); - } - } -} - -std::unique_ptr Agent::openPrimaryBuffer() -{ - // If we're using a separate buffer for stderr, and a program were to - // activate the stderr buffer, then we could accidentally scrape the same - // buffer twice. That probably shouldn't happen in ordinary use, but it - // can be avoided anyway by using the original console screen buffer in - // that mode. - if (!m_useConerr) { - return Win32ConsoleBuffer::openConout(); - } else { - return Win32ConsoleBuffer::openStdout(); - } -} - -void Agent::resizeWindow(int cols, int rows) -{ - ASSERT(cols >= 1 && rows >= 1); - cols = std::min(cols, MAX_CONSOLE_WIDTH); - rows = std::min(rows, MAX_CONSOLE_HEIGHT); - - Win32Console::FreezeGuard guard(m_console, m_console.frozen()); - const Coord newSize(cols, rows); - ConsoleScreenBufferInfo info; - auto primaryBuffer = openPrimaryBuffer(); - m_primaryScraper->resizeWindow(*primaryBuffer, newSize, info); - m_consoleInput->setMouseWindowRect(info.windowRect()); - if (m_errorScraper) { - m_errorScraper->resizeWindow(*m_errorBuffer, newSize, info); - } - - // Synthesize a WINDOW_BUFFER_SIZE_EVENT event. Normally, Windows - // generates this event only when the buffer size changes, not when the - // window size changes. This behavior is undesirable in two ways: - // - When winpty expands the window horizontally, it must expand the - // buffer first, then the window. At least some programs (e.g. the WSL - // bash.exe wrapper) use the window width rather than the buffer width, - // so there is a short timespan during which they can read the wrong - // value. - // - If the window's vertical size is changed, no event is generated, - // even though a typical well-behaved console program cares about the - // *window* height, not the *buffer* height. - // This synthesization works around a design flaw in the console. It's probably - // harmless. See https://github.com/rprichard/winpty/issues/110. - INPUT_RECORD sizeEvent {}; - sizeEvent.EventType = WINDOW_BUFFER_SIZE_EVENT; - sizeEvent.Event.WindowBufferSizeEvent.dwSize = primaryBuffer->bufferSize(); - DWORD actual {}; - WriteConsoleInputW(GetStdHandle(STD_INPUT_HANDLE), &sizeEvent, 1, &actual); -} - -void Agent::scrapeBuffers() -{ - Win32Console::FreezeGuard guard(m_console, m_console.frozen()); - ConsoleScreenBufferInfo info; - m_primaryScraper->scrapeBuffer(*openPrimaryBuffer(), info); - m_consoleInput->setMouseWindowRect(info.windowRect()); - if (m_errorScraper) { - m_errorScraper->scrapeBuffer(*m_errorBuffer, info); - } -} - -void Agent::syncConsoleTitle() -{ - std::wstring newTitle = m_console.title(); - if (newTitle != m_currentTitle) { - std::string command = std::string("\x1b]0;") + - utf8FromWide(newTitle) + "\x07"; - m_conoutPipe->write(command.c_str()); - m_currentTitle = newTitle; - } -}