// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "VtInputThread.hpp" #include "../interactivity/inc/ServiceLocator.hpp" #include "input.h" #include "../terminal/parser/InputStateMachineEngine.hpp" #include "outputStream.hpp" // For ConhostInternalGetSet #include "../terminal/adapter/InteractDispatch.hpp" #include "../types/inc/convert.hpp" #include "server.h" #include "output.h" #include "handle.h" using namespace Microsoft::Console; using namespace Microsoft::Console::Interactivity; using namespace Microsoft::Console::VirtualTerminal; // Constructor Description: // - Creates the VT Input Thread. // Arguments: // - hPipe - a handle to the file representing the read end of the VT pipe. // - inheritCursor - a bool indicating if the state machine should expect a // cursor positioning sequence. See MSFT:15681311. VtInputThread::VtInputThread(wil::unique_hfile hPipe, wil::shared_event shutdownEvent, const bool inheritCursor) : _hFile{ std::move(hPipe) }, _shutdownEvent{ shutdownEvent }, _hThread{}, _utf8Parser{ CP_UTF8 }, _dwThreadId{ 0 } { THROW_HR_IF(E_HANDLE, !_shutdownEvent); THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE); CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto pGetSet = std::make_unique(gci); THROW_IF_NULL_ALLOC(pGetSet.get()); auto engine = std::make_unique(new InteractDispatch(pGetSet.release()), inheritCursor); THROW_IF_NULL_ALLOC(engine.get()); _pInputStateMachine = std::make_unique(engine.release()); THROW_IF_NULL_ALLOC(_pInputStateMachine.get()); _shutdownWatchdog = std::async(std::launch::async, [&] { _shutdownEvent.wait(); if (_dwThreadId != 0) { wil::unique_handle threadHandle(OpenThread(STANDARD_RIGHTS_ALL | THREAD_TERMINATE, FALSE, _dwThreadId)); LOG_LAST_ERROR_IF_NULL(threadHandle.get()); if (threadHandle) { LOG_IF_WIN32_BOOL_FALSE(CancelSynchronousIo(threadHandle.get())); } } }); } VtInputThread::~VtInputThread() { if (_shutdownEvent) { _shutdownEvent.SetEvent(); // Wait for watchdog future to get the memo or we might try to destroy it before it gets to work. _shutdownWatchdog.wait(); } } // Method Description: // - Processes a buffer of input characters. The characters should be utf-8 // encoded, and will get converted to wchar_t's to be processed by the // input state machine. // Arguments: // - charBuffer - the UTF-8 characters recieved. // - cch - number of UTF-8 characters in charBuffer // Return Value: // - S_OK on success, otherwise an appropriate failure. [[nodiscard]] HRESULT VtInputThread::_HandleRunInput(_In_reads_(cch) const byte* const charBuffer, const int cch) { // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. // Only the global unlock attempts to dispatch ctrl events. If you use the // gci's unlock, when you press C-c, it won't be dispatched until the // next console API call. For something like `powershell sleep 60`, // that won't happen for 60s LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); try { std::unique_ptr pwsSequence; unsigned int cchConsumed; unsigned int cchSequence; auto hr = _utf8Parser.Parse(charBuffer, cch, cchConsumed, pwsSequence, cchSequence); // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. if (FAILED(hr)) { return S_FALSE; } _pInputStateMachine->ProcessString(pwsSequence.get(), cchSequence); } CATCH_RETURN(); return S_OK; } // Function Description: // - Static function used for initializing an instance's ThreadProc. // Arguments: // - lpParameter - A pointer to the VtInputThread instance that should be called. // Return Value: // - The return value of the underlying instance's _InputThread DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) { VtInputThread* const pInstance = reinterpret_cast(lpParameter); return pInstance->_InputThread(); } // Routine Description: // - A public way of pumping a single input message through the VT input channel // - Reading input can be a blocking operation. This function will capture // the thread ID of whomever calls it so it can be unblocked on shutdown events // by a watchdog thread. // - This function cannot be called by two public methods simultaneously. // If another is already waiting in a blocked read on the VT input thread, // an invalid state error will be returned. // - This function is only valid during startup. Once the real VtInputThread starts // to process the input, it will fill the thread ID field permanently until shutdown. // Arguments: // - // Return Value: // - S_OK, a ReadFile error, an error processing input, or an invalid state error if another thread is already waiting. [[nodiscard]] HRESULT VtInputThread::DoReadInput() { // If there's already a thread pumping VT input, it's not valid to read this from the outside. RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), _dwThreadId != 0); // Store which thread is attempting to read VT input. It may get blocked indefinitely and need // to get unstuck by a shutdown event. _dwThreadId = GetCurrentThreadId(); // Set it back to 0 on the way out. auto restoreThreadId = wil::scope_exit([&] { _dwThreadId = 0; }); // Perform the blocking read operation. return _ReadInput(); } // Method Description: // - Do a single ReadFile from our pipe, and try and handle it. // Arguments: // - // Return Value: // - S_OK or relevant error [[nodiscard]] HRESULT VtInputThread::_ReadInput() { byte buffer[256]; DWORD dwRead = 0; RETURN_IF_WIN32_BOOL_FALSE(ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr)); RETURN_IF_FAILED(_HandleRunInput(buffer, dwRead)); return S_OK; } // Method Description: // - The ThreadProc for the VT Input Thread. Reads input from the pipe, and // passes it to _HandleRunInput to be processed by the // InputStateMachineEngine. // Return Value: // - Any error from reading the pipe or writing to the input buffer that might // have caused us to exit. DWORD VtInputThread::_InputThread() { auto onExitTriggerShutdown = wil::scope_exit([&] { _shutdownEvent.SetEvent(); }); while (true) { // NOTE: From inside the thread itself, we don't need to stash the thread handle each call // because it was done permanently for us when the thread was created. No one else is allowed // in through the public method while the actual VtInputThread is running. Only during startup. RETURN_IF_FAILED(_ReadInput()); } return S_OK; } // Method Description: // - Starts the VT input thread. [[nodiscard]] HRESULT VtInputThread::Start() { RETURN_HR_IF(E_HANDLE, !_hFile); HANDLE hThread = nullptr; // 0 is the right value, https://blogs.msdn.microsoft.com/oldnewthing/20040223-00/?p=40503 DWORD dwThreadId = 0; hThread = CreateThread(nullptr, 0, VtInputThread::StaticVtInputThreadProc, this, 0, &dwThreadId); RETURN_LAST_ERROR_IF_NULL(hThread); _hThread.reset(hThread); // This will permanently shut the door on the public read method until shutdown. // Once the thread is servicing messages, we don't want any other threads getting in here. _dwThreadId = dwThreadId; return S_OK; }