From 599b55081762af1594cd8419320e79b9be533944 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 28 Feb 2023 21:55:18 +0100 Subject: [PATCH] Remove TranslateUnicodeToOem and all related code (#14745) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overarching intention of this PR is to improve our Unicode support. Most of our APIs still don't support anything beyond UCS-2 and DBCS sequences. This commit doesn't fix the UTF-16 support (by supporting surrogate pairs), but it does improve support for UTF-8 by allowing longer `char` sequences. It does so by removing `TranslateUnicodeToOem` which seems to have had an almost viral effect on code quality wherever it was used. It made the assumption that _all_ narrow glyphs encode to 1 `char` and most wide glyphs to 2 `char`s. It also didn't bother to check whether `WideCharToMultiByte` failed or returned a different amount of `char`s. So up until now it was easily possible to read uninitialized stack memory from conhost. Any code that used this function was forced to do the same "measurement" of narrow/wide glyphs, because _of course_ it didn't had any way to indicate to the caller how much memory it needs to store the result. Instead all callers were forced to sorta replicate how it worked to calculate the required storage ahead of time. Unsurprisingly, none of the callers used the same algorithm... Without it the code is much leaner and easier to understand now. The best example is `COOKED_READ_DATA::_handlePostCharInputLoop` which used to contain 3 blocks of _almost_ identical code, but with ever so subtle differences. After reading the old code for hours I still don't know if they were relevant or not. It used to be 200 lines of code lacking any documentation and it's now 50 lines with descriptive function names. I hope this doesn't break anything, but to be honest I can't imagine anyone having relied on this mess in the first place. I needed some helpers to handle byte slices (`std::span`), which is why a new `til/bytes.h` header was added. Initially I wrote a `buf_writer` class but felt like such a wrapper around a slice/span was annoying to use. As such I've opted for freestanding functions which take slices as mutable references and "advance" them (offset the start) whenever they're read from or written to. I'm not particularly happy with the design but they do the job. Related to #8000 Fixes #4551 Fixes #7589 Fixes #8663 ## Validation Steps Performed * Unit and feature tests ✅ * Far Manager ✅ * Fixes test cases in #4551, #7589 and #8663 ✅ --- src/host/dbcs.cpp | 74 --- src/host/dbcs.h | 7 - src/host/directio.cpp | 68 +-- src/host/inputBuffer.cpp | 461 ++++++++++-------- src/host/inputBuffer.hpp | 38 +- src/host/misc.cpp | 48 +- src/host/misc.h | 2 - src/host/readDataCooked.cpp | 202 +------- src/host/readDataCooked.hpp | 4 +- src/host/readDataDirect.cpp | 135 ++--- src/host/readDataDirect.hpp | 3 - src/host/readDataRaw.cpp | 131 +---- src/host/stream.cpp | 307 ++---------- src/host/stream.h | 6 + src/host/ut_host/CommandLineTests.cpp | 2 +- src/host/ut_host/Host.UnitTests.vcxproj | 1 - .../ut_host/Host.UnitTests.vcxproj.filters | 3 - src/host/ut_host/InputBufferTests.cpp | 92 ++-- src/host/ut_host/ReadWaitTests.cpp | 129 ----- src/inc/til/bytes.h | 57 +++ src/inc/til/type_traits.h | 48 ++ src/server/ApiDispatchers.cpp | 7 +- src/types/inc/IInputEvent.hpp | 2 + 23 files changed, 603 insertions(+), 1224 deletions(-) delete mode 100644 src/host/ut_host/ReadWaitTests.cpp create mode 100644 src/inc/til/bytes.h create mode 100644 src/inc/til/type_traits.h diff --git a/src/host/dbcs.cpp b/src/host/dbcs.cpp index 0048f5cdc4a..bed34bfdca0 100644 --- a/src/host/dbcs.cpp +++ b/src/host/dbcs.cpp @@ -177,77 +177,3 @@ BOOL IsAvailableEastAsianCodePage(const UINT uiCodePage) return false; } } - -_Ret_range_(0, cbAnsi) - ULONG TranslateUnicodeToOem(_In_reads_(cchUnicode) PCWCHAR pwchUnicode, - const ULONG cchUnicode, - _Out_writes_bytes_(cbAnsi) PCHAR pchAnsi, - const ULONG cbAnsi, - _Out_ std::unique_ptr& partialEvent) -{ - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto TmpUni = new (std::nothrow) WCHAR[cchUnicode]; - if (TmpUni == nullptr) - { - return 0; - } - - memcpy(TmpUni, pwchUnicode, cchUnicode * sizeof(WCHAR)); - - CHAR AsciiDbcs[2]; - AsciiDbcs[1] = 0; - - ULONG i, j; - for (i = 0, j = 0; i < cchUnicode && j < cbAnsi; i++, j++) - { - if (IsGlyphFullWidth(TmpUni[i])) - { - const auto NumBytes = sizeof(AsciiDbcs); - ConvertToOem(gci.CP, &TmpUni[i], 1, (LPSTR)&AsciiDbcs[0], NumBytes); - if (IsDBCSLeadByteConsole(AsciiDbcs[0], &gci.CPInfo)) - { - if (j < cbAnsi - 1) - { // -1 is safe DBCS in buffer - pchAnsi[j] = AsciiDbcs[0]; - j++; - pchAnsi[j] = AsciiDbcs[1]; - AsciiDbcs[1] = 0; - } - else - { - pchAnsi[j] = AsciiDbcs[0]; - break; - } - } - else - { - pchAnsi[j] = AsciiDbcs[0]; - AsciiDbcs[1] = 0; - } - } - else - { - ConvertToOem(gci.CP, &TmpUni[i], 1, &pchAnsi[j], 1); - } - } - - if (AsciiDbcs[1]) - { - try - { - auto keyEvent = std::make_unique(); - if (keyEvent.get()) - { - keyEvent->SetCharData(AsciiDbcs[1]); - partialEvent.reset(static_cast(keyEvent.release())); - } - } - catch (...) - { - LOG_HR(wil::ResultFromCaughtException()); - } - } - - delete[] TmpUni; - return j; -} diff --git a/src/host/dbcs.h b/src/host/dbcs.h index fd4737e7a9c..db651eadef9 100644 --- a/src/host/dbcs.h +++ b/src/host/dbcs.h @@ -29,10 +29,3 @@ bool IsDBCSLeadByteConsole(const CHAR ch, const CPINFO* const pCPInfo); BYTE CodePageToCharSet(const UINT uiCodePage); BOOL IsAvailableEastAsianCodePage(const UINT uiCodePage); - -_Ret_range_(0, cbAnsi) - ULONG TranslateUnicodeToOem(_In_reads_(cchUnicode) PCWCHAR pwchUnicode, - const ULONG cchUnicode, - _Out_writes_bytes_(cbAnsi) PCHAR pchAnsi, - const ULONG cbAnsi, - _Out_ std::unique_ptr& partialEvent); diff --git a/src/host/directio.cpp b/src/host/directio.cpp index c5e69e454ad..96661c60b70 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -169,75 +169,21 @@ void EventsToUnicode(_Inout_ std::deque>& inEvents, LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - std::deque> partialEvents; - if (!IsUnicode) - { - if (inputBuffer.IsReadPartialByteSequenceAvailable()) - { - partialEvents.push_back(inputBuffer.FetchReadPartialByteSequence(IsPeek)); - } - } - - size_t amountToRead; - if (FAILED(SizeTSub(eventReadCount, partialEvents.size(), &amountToRead))) - { - return STATUS_INTEGER_OVERFLOW; - } - std::deque> readEvents; - auto Status = inputBuffer.Read(readEvents, - amountToRead, - IsPeek, - true, - IsUnicode, - false); + const auto Status = inputBuffer.Read(outEvents, + eventReadCount, + IsPeek, + true, + IsUnicode, + false); if (CONSOLE_STATUS_WAIT == Status) { - FAIL_FAST_IF(!(readEvents.empty())); // If we're told to wait until later, move all of our context // to the read data object and send it back up to the server. waiter = std::make_unique(&inputBuffer, &readHandleState, eventReadCount, - std::move(partialEvents)); - } - else if (SUCCEEDED_NTSTATUS(Status)) - { - // split key events to oem chars if necessary - if (!IsUnicode) - { - try - { - SplitToOem(readEvents); - } - CATCH_LOG(); - } - - // combine partial and readEvents - while (!partialEvents.empty()) - { - readEvents.push_front(std::move(partialEvents.back())); - partialEvents.pop_back(); - } - - // move events over - for (size_t i = 0; i < eventReadCount; ++i) - { - if (readEvents.empty()) - { - break; - } - outEvents.push_back(std::move(readEvents.front())); - readEvents.pop_front(); - } - - // store partial event if necessary - if (!readEvents.empty()) - { - inputBuffer.StoreReadPartialByteSequence(std::move(readEvents.front())); - readEvents.pop_front(); - FAIL_FAST_IF(!(readEvents.empty())); - } + std::move(outEvents)); } return Status; } diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp index 2427d4c19ed..215b49ceb69 100644 --- a/src/host/inputBuffer.cpp +++ b/src/host/inputBuffer.cpp @@ -3,12 +3,13 @@ #include "precomp.h" #include "inputBuffer.hpp" -#include "dbcs.h" + #include "stream.h" #include "../types/inc/GlyphWidth.hpp" -#include +#include +#include "misc.h" #include "../interactivity/inc/ServiceLocator.hpp" #define INPUT_BUFFER_DEFAULT_INPUT_MODE (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT) @@ -36,59 +37,199 @@ InputBuffer::InputBuffer() : fInComposition = false; } -// Routine Description: -// - This routine frees the resources associated with an input buffer. -// Arguments: -// - None -// Return Value: -InputBuffer::~InputBuffer() = default; +// Transfer as many `wchar_t`s from source over to the `char`/`wchar_t` buffer `target`. After it returns, +// the start of the `source` and `target` slices will be offset by as many bytes as have been copied +// over, so that if you call this function again it'll continue copying from wherever it left off. +// +// It performs the necessary `WideCharToMultiByte` conversion if `isUnicode` is `false`. +// Since not all converted `char`s might fit into `target` it'll cache the remainder. The next +// time this function is called those cached `char`s will then be the first to be copied over. +void InputBuffer::Consume(bool isUnicode, std::wstring_view& source, std::span& target) +{ + // `_cachedTextReaderA` might still contain target data from a previous invocation. + // `ConsumeCached` calls `_switchReadingMode` for us. + ConsumeCached(isUnicode, target); -// Routine Description: -// - checks if any partial char data is available for reading operation -// Arguments: -// - None -// Return Value: -// - true if partial char data is available, false otherwise -bool InputBuffer::IsReadPartialByteSequenceAvailable() + if (source.empty() || target.empty()) + { + return; + } + + if (isUnicode) + { + // The above block should either leave `target` or `_cachedTextReaderW` empty (or both). + // If we're here, `_cachedTextReaderW` should be empty. + assert(_cachedTextReaderW.empty()); + + til::bytes_transfer(target, source); + } + else + { + // The above block should either leave `target` or `_cachedTextReaderA` empty (or both). + // If we're here, `_cachedTextReaderA` should be empty. + assert(_cachedTextReaderA.empty()); + + const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().CP; + + // Fast path: Batch convert all data in case the user provided buffer is large enough. + { + const auto wideLength = gsl::narrow(source.size()); + const auto narrowLength = gsl::narrow(target.size()); + + const auto length = WideCharToMultiByte(cp, 0, source.data(), wideLength, target.data(), narrowLength, nullptr, nullptr); + if (length > 0) + { + source = {}; + til::bytes_advance(target, gsl::narrow_cast(length)); + return; + } + + const auto error = GetLastError(); + THROW_HR_IF(HRESULT_FROM_WIN32(error), error != ERROR_INSUFFICIENT_BUFFER); + } + + // Slow path: Character-wise conversion otherwise. We do this in order to only + // consume as many characters from `source` as necessary to fill `target`. + { + size_t read = 0; + + for (const auto& wch : source) + { + char buffer[8]; + const auto length = WideCharToMultiByte(cp, 0, &wch, 1, &buffer[0], sizeof(buffer), nullptr, nullptr); + THROW_LAST_ERROR_IF(length <= 0); + + std::string_view slice{ &buffer[0], gsl::narrow_cast(length) }; + til::bytes_transfer(target, slice); + + ++read; + + if (!slice.empty()) + { + _cachedTextA = slice; + _cachedTextReaderA = _cachedTextA; + break; + } + } + + source = source.substr(read); + } + } +} + +// Same as `Consume`, but without any `source` characters. +void InputBuffer::ConsumeCached(bool isUnicode, std::span& target) { - return _readPartialByteSequence.get() != nullptr; + _switchReadingMode(isUnicode ? ReadingMode::StringW : ReadingMode::StringA); + + if (isUnicode) + { + if (!_cachedTextReaderW.empty()) + { + til::bytes_transfer(target, _cachedTextReaderW); + + if (_cachedTextReaderW.empty()) + { + // This is just so that we release memory eagerly. + _cachedTextW = std::wstring{}; + } + } + } + else + { + if (!_cachedTextReaderA.empty()) + { + til::bytes_transfer(target, _cachedTextReaderA); + + if (_cachedTextReaderA.empty()) + { + // This is just so that we release memory eagerly. + _cachedTextA = std::string{}; + } + } + } } -// Routine Description: -// - reads any read partial char data available -// Arguments: -// - peek - if true, data will not be removed after being fetched -// Return Value: -// - the partial char data. may be nullptr if no data is available -std::unique_ptr InputBuffer::FetchReadPartialByteSequence(_In_ bool peek) +void InputBuffer::Cache(std::wstring_view source) +{ + const auto off = _cachedTextW.empty() ? 0 : _cachedTextReaderW.data() - _cachedTextW.data(); + _cachedTextW.append(source); + _cachedTextReaderW = std::wstring_view{ _cachedTextW }.substr(off); +} + +// Moves up to `count`, previously cached events into `target`. +size_t InputBuffer::ConsumeCached(bool isUnicode, size_t count, InputEventQueue& target) { - if (!IsReadPartialByteSequenceAvailable()) + _switchReadingMode(isUnicode ? ReadingMode::InputEventsW : ReadingMode::InputEventsA); + + size_t i = 0; + + while (i < count && !_cachedInputEvents.empty()) { - return std::unique_ptr(); + target.push_back(std::move(_cachedInputEvents.front())); + _cachedInputEvents.pop_front(); + i++; } - if (peek) + return i; +} + +// Copies up to `count`, previously cached events into `target`. +size_t InputBuffer::PeekCached(bool isUnicode, size_t count, InputEventQueue& target) +{ + _switchReadingMode(isUnicode ? ReadingMode::InputEventsW : ReadingMode::InputEventsA); + + size_t i = 0; + + for (const auto& e : _cachedInputEvents) { - return IInputEvent::Create(_readPartialByteSequence->ToInputRecord()); + if (i >= count) + { + break; + } + + target.push_back(IInputEvent::Create(e->ToInputRecord())); + i++; } - else + + return i; +} + +// Trims `source` to have a size below or equal to `expectedSourceSize` by +// storing any extra events in `_cachedInputEvents` for later retrieval. +void InputBuffer::Cache(bool isUnicode, InputEventQueue& source, size_t expectedSourceSize) +{ + _switchReadingMode(isUnicode ? ReadingMode::InputEventsW : ReadingMode::InputEventsA); + + if (source.size() > expectedSourceSize) { - std::unique_ptr outEvent; - outEvent.swap(_readPartialByteSequence); - return outEvent; + _cachedInputEvents.insert( + _cachedInputEvents.end(), + std::make_move_iterator(source.begin() + expectedSourceSize), + std::make_move_iterator(source.end())); + source.resize(expectedSourceSize); } } -// Routine Description: -// - stores partial read char data for a later read. will overwrite -// any previously stored data. -// Arguments: -// - event - The event to store -// Return Value: -// - None -void InputBuffer::StoreReadPartialByteSequence(std::unique_ptr event) +void InputBuffer::_switchReadingMode(ReadingMode mode) +{ + if (_readingMode != mode) + { + _switchReadingModeSlowPath(mode); + } +} + +void InputBuffer::_switchReadingModeSlowPath(ReadingMode mode) { - _readPartialByteSequence.swap(event); + _cachedTextA = std::string{}; + _cachedTextReaderA = {}; + + _cachedTextW = std::wstring{}; + _cachedTextReaderW = {}; + + _cachedInputEvents = std::deque>{}; + + _readingMode = mode; } // Routine Description: @@ -261,47 +402,105 @@ void InputBuffer::PassThroughWin32MouseRequest(bool enable) const bool WaitForData, const bool Unicode, const bool Stream) +try { - try + assert(OutEvents.empty()); + + const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().CP; + + if (Peek) + { + PeekCached(Unicode, AmountToRead, OutEvents); + } + else + { + ConsumeCached(Unicode, AmountToRead, OutEvents); + } + + auto it = _storage.begin(); + const auto end = _storage.end(); + + while (it != end && OutEvents.size() < AmountToRead) { - if (_storage.empty()) + auto event = IInputEvent::Create((*it)->ToInputRecord()); + + if (event->EventType() == InputEventType::KeyEvent) { - if (!WaitForData) + const auto keyEvent = static_cast(event.get()); + WORD repeat = 1; + + // for stream reads we need to split any key events that have been coalesced + if (Stream) { - return STATUS_SUCCESS; + repeat = keyEvent->GetRepeatCount(); + keyEvent->SetRepeatCount(1); } - return CONSOLE_STATUS_WAIT; - } - // read from buffer - std::deque> events; - size_t eventsRead; - bool resetWaitEvent; - _ReadBuffer(events, - AmountToRead, - eventsRead, - Peek, - resetWaitEvent, - Unicode, - Stream); - - // copy events to outEvents - while (!events.empty()) - { - OutEvents.push_back(std::move(events.front())); - events.pop_front(); - } + if (Unicode) + { + do + { + OutEvents.push_back(std::make_unique(*keyEvent)); + repeat--; + } while (repeat > 0 && OutEvents.size() < AmountToRead); + } + else + { + const auto wch = keyEvent->GetCharData(); + + char buffer[8]; + const auto length = WideCharToMultiByte(cp, 0, &wch, 1, &buffer[0], sizeof(buffer), nullptr, nullptr); + THROW_LAST_ERROR_IF(length <= 0); + + const std::string_view str{ &buffer[0], gsl::narrow_cast(length) }; + + do + { + for (const auto& ch : str) + { + auto tempEvent = std::make_unique(*keyEvent); + tempEvent->SetCharData(ch); + OutEvents.push_back(std::move(tempEvent)); + } + repeat--; + } while (repeat > 0 && OutEvents.size() < AmountToRead); + } - if (resetWaitEvent) + if (repeat && !Peek) + { + const auto originalKeyEvent = static_cast((*it).get()); + originalKeyEvent->SetRepeatCount(repeat); + break; + } + } + else { - ServiceLocator::LocateGlobals().hInputEvent.ResetEvent(); + OutEvents.push_back(std::move(event)); } - return STATUS_SUCCESS; + + ++it; } - catch (...) + + if (!Peek) + { + _storage.erase(_storage.begin(), it); + } + + Cache(Unicode, OutEvents, AmountToRead); + + if (OutEvents.empty()) + { + return WaitForData ? CONSOLE_STATUS_WAIT : STATUS_SUCCESS; + } + if (_storage.empty()) { - return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException()); + ServiceLocator::LocateGlobals().hInputEvent.ResetEvent(); } + return STATUS_SUCCESS; +} +catch (...) +{ + return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException()); } // Routine Description: @@ -349,130 +548,6 @@ void InputBuffer::PassThroughWin32MouseRequest(bool enable) return Status; } -// Routine Description: -// - This routine reads from a buffer. It does the buffer manipulation. -// Arguments: -// - outEvents - where read events are placed -// - readCount - amount of events to read -// - eventsRead - where to store number of events read -// - peek - if true , don't remove data from buffer, just copy it. -// - resetWaitEvent - on exit, true if buffer became empty. -// - unicode - true if read should be done in unicode mode -// - streamRead - true if read should unpack KeyEvents that have a >1 repeat count. readCount must be 1 if streamRead is true. -// Return Value: -// - -// Note: -// - The console lock must be held when calling this routine. -void InputBuffer::_ReadBuffer(_Out_ std::deque>& outEvents, - const size_t readCount, - _Out_ size_t& eventsRead, - const bool peek, - _Out_ bool& resetWaitEvent, - const bool unicode, - const bool streamRead) -{ - // when stream reading, the previous behavior was to only allow reading of a single - // event at a time. - FAIL_FAST_IF(streamRead && readCount != 1); - - resetWaitEvent = false; - - std::deque> readEvents; - // we need another var to keep track of how many we've read - // because dbcs records count for two when we aren't doing a - // unicode read but the eventsRead count should return the number - // of events actually put into outRecords. - size_t virtualReadCount = 0; - - while (!_storage.empty() && virtualReadCount < readCount) - { - auto performNormalRead = true; - // for stream reads we need to split any key events that have been coalesced - if (streamRead) - { - if (_storage.front()->EventType() == InputEventType::KeyEvent) - { - const auto pKeyEvent = static_cast(_storage.front().get()); - if (pKeyEvent->GetRepeatCount() > 1) - { - // split the key event - auto streamKeyEvent = std::make_unique(*pKeyEvent); - streamKeyEvent->SetRepeatCount(1); - readEvents.push_back(std::move(streamKeyEvent)); - pKeyEvent->SetRepeatCount(pKeyEvent->GetRepeatCount() - 1); - performNormalRead = false; - } - } - } - - if (performNormalRead) - { - readEvents.push_back(std::move(_storage.front())); - _storage.pop_front(); - } - - ++virtualReadCount; - if (!unicode) - { - if (readEvents.back()->EventType() == InputEventType::KeyEvent) - { - const auto pKeyEvent = static_cast(readEvents.back().get()); - if (IsGlyphFullWidth(pKeyEvent->GetCharData())) - { - ++virtualReadCount; - } - } - } - } - - // the amount of events that were actually read - eventsRead = readEvents.size(); - - // copy the events back if we were supposed to peek - if (peek) - { - if (streamRead) - { - // we need to check and see if the event was split from a coalesced key event - // or if it was unrelated to the current front event in storage - if (!readEvents.empty() && - !_storage.empty() && - readEvents.back()->EventType() == InputEventType::KeyEvent && - _storage.front()->EventType() == InputEventType::KeyEvent && - _CanCoalesce(static_cast(*readEvents.back()), - static_cast(*_storage.front()))) - { - auto& keyEvent = static_cast(*_storage.front()); - keyEvent.SetRepeatCount(keyEvent.GetRepeatCount() + 1); - } - else - { - _storage.push_front(IInputEvent::Create(readEvents.back()->ToInputRecord())); - } - } - else - { - for (auto it = readEvents.crbegin(); it != readEvents.crend(); ++it) - { - _storage.push_front(IInputEvent::Create((*it)->ToInputRecord())); - } - } - } - - // move events read to proper deque - while (!readEvents.empty()) - { - outEvents.push_back(std::move(readEvents.front())); - readEvents.pop_front(); - } - - // signal if we emptied the buffer - if (_storage.empty()) - { - resetWaitEvent = true; - } -} - // Routine Description: // - Writes events to the beginning of the input buffer. // Arguments: diff --git a/src/host/inputBuffer.hpp b/src/host/inputBuffer.hpp index 5c003faf03c..f5103ea9f98 100644 --- a/src/host/inputBuffer.hpp +++ b/src/host/inputBuffer.hpp @@ -41,12 +41,15 @@ class InputBuffer final : public ConsoleObjectHeader bool fInComposition; // specifies if there's an ongoing text composition InputBuffer(); - ~InputBuffer(); - // storage API for partial dbcs bytes being read from the buffer - bool IsReadPartialByteSequenceAvailable(); - std::unique_ptr FetchReadPartialByteSequence(_In_ bool peek); - void StoreReadPartialByteSequence(std::unique_ptr event); + // String oriented APIs + void Consume(bool isUnicode, std::wstring_view& source, std::span& target); + void ConsumeCached(bool isUnicode, std::span& target); + void Cache(std::wstring_view source); + // INPUT_RECORD oriented APIs + size_t ConsumeCached(bool isUnicode, size_t count, InputEventQueue& target); + size_t PeekCached(bool isUnicode, size_t count, InputEventQueue& target); + void Cache(bool isUnicode, InputEventQueue& source, size_t expectedSourceSize); // storage API for partial dbcs bytes being written to the buffer bool IsWritePartialByteSequenceAvailable(); @@ -84,8 +87,22 @@ class InputBuffer final : public ConsoleObjectHeader void PassThroughWin32MouseRequest(bool enable); private: + enum class ReadingMode : uint8_t + { + StringA, + StringW, + InputEventsA, + InputEventsW, + }; + + std::string _cachedTextA; + std::string_view _cachedTextReaderA; + std::wstring _cachedTextW; + std::wstring_view _cachedTextReaderW; + std::deque> _cachedInputEvents; + ReadingMode _readingMode = ReadingMode::StringA; + std::deque> _storage; - std::unique_ptr _readPartialByteSequence; std::unique_ptr _writePartialByteSequence; Microsoft::Console::VirtualTerminal::TerminalInput _termInput; Microsoft::Console::Render::VtEngine* _pTtyConnection; @@ -96,13 +113,8 @@ class InputBuffer final : public ConsoleObjectHeader // Otherwise, we should be calling them. bool _vtInputShouldSuppress{ false }; - void _ReadBuffer(_Out_ std::deque>& outEvents, - const size_t readCount, - _Out_ size_t& eventsRead, - const bool peek, - _Out_ bool& resetWaitEvent, - const bool unicode, - const bool streamRead); + void _switchReadingMode(ReadingMode mode); + void _switchReadingModeSlowPath(ReadingMode mode); void _WriteBuffer(_Inout_ std::deque>& inRecords, _Out_ size_t& eventsWritten, diff --git a/src/host/misc.cpp b/src/host/misc.cpp index 671da86d7a6..531ec2a9a63 100644 --- a/src/host/misc.cpp +++ b/src/host/misc.cpp @@ -2,13 +2,12 @@ // Licensed under the MIT license. #include "precomp.h" - #include "misc.h" -#include "dbcs.h" -#include "../types/inc/convert.hpp" -#include "../types/inc/GlyphWidth.hpp" +#include +#include "dbcs.h" +#include "../types/inc/GlyphWidth.hpp" #include "../interactivity/inc/ServiceLocator.hpp" #pragma hdrstop @@ -182,47 +181,6 @@ BOOL CheckBisectProcessW(const SCREEN_INFORMATION& ScreenInfo, } } -// Routine Description: -// - Converts all key events in the deque to the oem char data and adds -// them back to events. -// Arguments: -// - events - on input the IInputEvents to convert. on output, the -// converted input events -// Note: may throw on error -void SplitToOem(std::deque>& events) -{ - const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().CP; - std::deque> convertedEvents; - - for (auto& currentEvent : events) - { - if (currentEvent->EventType() == InputEventType::KeyEvent) - { - const auto pKeyEvent = static_cast(currentEvent.get()); - const auto wch = pKeyEvent->GetCharData(); - - char buffer[8]; - const auto length = WideCharToMultiByte(cp, 0, &wch, 1, &buffer[0], sizeof(buffer), nullptr, nullptr); - THROW_LAST_ERROR_IF(length <= 0); - - const std::string_view str{ &buffer[0], gsl::narrow_cast(length) }; - - for (const auto& ch : str) - { - auto tempEvent = std::make_unique(*pKeyEvent); - tempEvent->SetCharData(ch); - convertedEvents.push_back(std::move(tempEvent)); - } - } - else - { - convertedEvents.push_back(std::move(currentEvent)); - } - } - - events = std::move(convertedEvents); -} - // Routine Description: // - Converts unicode characters to ANSI given a destination codepage // Arguments: diff --git a/src/host/misc.h b/src/host/misc.h index 6269353f5c0..ca90ac23ac8 100644 --- a/src/host/misc.h +++ b/src/host/misc.h @@ -44,8 +44,6 @@ int ConvertToOem(const UINT uiCodePage, _Out_writes_(cchTarget) CHAR* const pchTarget, const UINT cchTarget) noexcept; -void SplitToOem(std::deque>& events); - int ConvertInputToUnicode(const UINT uiCodePage, _In_reads_(cchSource) const CHAR* const pchSource, const UINT cchSource, diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 0ea9e73cb54..15f6e49d310 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -47,7 +47,7 @@ COOKED_READ_DATA::COOKED_READ_DATA(_In_ InputBuffer* const pInputBuffer, _In_ INPUT_READ_HANDLE_DATA* const pInputReadHandleData, SCREEN_INFORMATION& screenInfo, _In_ size_t UserBufferSize, - _In_ PWCHAR UserBuffer, + _In_ char* UserBuffer, _In_ ULONG CtrlWakeupMask, _In_ const std::wstring_view exeName, _In_ const std::string_view initialData, @@ -62,7 +62,7 @@ COOKED_READ_DATA::COOKED_READ_DATA(_In_ InputBuffer* const pInputBuffer, _exeName{ exeName }, _pdwNumBytes{ nullptr }, - _commandHistory{ CommandHistory::s_Find((HANDLE)pClientProcess) }, + _commandHistory{ CommandHistory::s_Find(pClientProcess) }, _controlKeyState{ 0 }, _ctrlWakeupMask{ CtrlWakeupMask }, _visibleCharCount{ 0 }, @@ -881,10 +881,10 @@ size_t COOKED_READ_DATA::SavePromptToUserBuffer(const size_t cch) try { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto wstr = ConvertToW(gci.CP, { reinterpret_cast(_userBuffer), cch }); - const auto copyAmount = std::min(wstr.size(), _userBufferSize / sizeof(wchar_t)); - std::copy_n(wstr.begin(), copyAmount, _userBuffer); - return copyAmount * sizeof(wchar_t); + const auto wstr = ConvertToW(gci.CP, { _userBuffer, cch }); + const auto copyAmount = std::min(wstr.size() * sizeof(wchar_t), _userBufferSize); + std::copy_n(reinterpret_cast(wstr.data()), copyAmount, _userBuffer); + return copyAmount; } CATCH_LOG(); } @@ -1003,211 +1003,55 @@ void COOKED_READ_DATA::SavePendingInput(const size_t index, const bool multiline // - Status code that indicates success, out of memory, etc. [[nodiscard]] NTSTATUS COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& numBytes, ULONG& controlKeyState) noexcept { + std::span writer{ _userBuffer, _userBufferSize }; + std::wstring_view input{ _backupLimit, _bytesRead / sizeof(wchar_t) }; DWORD LineCount = 1; if (_echoInput) { - // Figure out where real string ends (at carriage return or end of buffer). - auto StringPtr = _backupLimit; - auto StringLength = _bytesRead / sizeof(WCHAR); - auto FoundCR = false; - for (size_t i = 0; i < StringLength; i++) - { - if (*StringPtr++ == UNICODE_CARRIAGERETURN) - { - StringLength = i; - FoundCR = true; - break; - } - } - - if (FoundCR) + const auto idx = input.find(UNICODE_CARRIAGERETURN); + if (idx != decltype(input)::npos) { if (_commandHistory) { - // add to command line recall list if we have a history list. auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - LOG_IF_FAILED(_commandHistory->Add({ _backupLimit, StringLength }, - WI_IsFlagSet(gci.Flags, CONSOLE_HISTORY_NODUP))); + LOG_IF_FAILED(_commandHistory->Add({ _backupLimit, idx }, WI_IsFlagSet(gci.Flags, CONSOLE_HISTORY_NODUP))); } - Tracing::s_TraceCookedRead(_clientProcess, - _backupLimit, - base::saturated_cast(StringLength)); - - // check for alias + Tracing::s_TraceCookedRead(_clientProcess, _backupLimit, base::saturated_cast(idx)); ProcessAliases(LineCount); - } - } - auto fAddDbcsLead = false; - size_t NumBytes = 0; - // at this point, a->NumBytes contains the number of bytes in - // the UNICODE string read. UserBufferSize contains the converted - // size of the app's buffer. - if (_bytesRead > _userBufferSize || LineCount > 1) - { - if (LineCount > 1) - { - PWSTR Tmp; - if (!isUnicode) + if (LineCount > 1) { - if (_pInputBuffer->IsReadPartialByteSequenceAvailable()) - { - fAddDbcsLead = true; - auto event = GetInputBuffer()->FetchReadPartialByteSequence(false); - const auto pKeyEvent = static_cast(event.get()); - *_userBuffer = static_cast(pKeyEvent->GetCharData()); - _userBuffer++; - _userBufferSize -= sizeof(wchar_t); - } - - NumBytes = 0; - for (Tmp = _backupLimit; - *Tmp != UNICODE_LINEFEED && _userBufferSize / sizeof(WCHAR) > NumBytes; - Tmp++) - { - NumBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1; - } + input = input.substr(0, idx + 1); } - - // clang-format off -#pragma prefast(suppress: __WARNING_BUFFER_OVERFLOW, "LineCount > 1 means there's a UNICODE_LINEFEED") - // clang-format on - for (Tmp = _backupLimit; *Tmp != UNICODE_LINEFEED; Tmp++) - { - FAIL_FAST_IF(!(Tmp < (_backupLimit + _bytesRead))); - } - - numBytes = (ULONG)(Tmp - _backupLimit + 1) * sizeof(*Tmp); - } - else - { - if (!isUnicode) - { - PWSTR Tmp; - - if (_pInputBuffer->IsReadPartialByteSequenceAvailable()) - { - fAddDbcsLead = true; - auto event = GetInputBuffer()->FetchReadPartialByteSequence(false); - const auto pKeyEvent = static_cast(event.get()); - *_userBuffer = static_cast(pKeyEvent->GetCharData()); - _userBuffer++; - _userBufferSize -= sizeof(wchar_t); - } - NumBytes = 0; - auto NumToWrite = _bytesRead; - for (Tmp = _backupLimit; - NumToWrite && _userBufferSize / sizeof(WCHAR) > NumBytes; - Tmp++, NumToWrite -= sizeof(WCHAR)) - { - NumBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1; - } - } - numBytes = _userBufferSize; } + } - __analysis_assume(numBytes <= _userBufferSize); - memmove(_userBuffer, _backupLimit, numBytes); + GetInputBuffer()->Consume(isUnicode, input, writer); - const auto pInputReadHandleData = GetInputReadHandleData(); - const std::wstring_view pending{ _backupLimit + (numBytes / sizeof(wchar_t)), (_bytesRead - numBytes) / sizeof(wchar_t) }; + if (!input.empty()) + { if (LineCount > 1) { - pInputReadHandleData->SaveMultilinePendingInput(pending); + GetInputReadHandleData()->SaveMultilinePendingInput(input); } else { - pInputReadHandleData->SavePendingInput(pending); + GetInputReadHandleData()->SavePendingInput(input); } } - else - { - if (!isUnicode) - { - PWSTR Tmp; - if (_pInputBuffer->IsReadPartialByteSequenceAvailable()) - { - fAddDbcsLead = true; - auto event = GetInputBuffer()->FetchReadPartialByteSequence(false); - const auto pKeyEvent = static_cast(event.get()); - *_userBuffer = static_cast(pKeyEvent->GetCharData()); - _userBuffer++; - _userBufferSize -= sizeof(wchar_t); - - if (_userBufferSize == 0) - { - numBytes = 1; - return STATUS_SUCCESS; - } - } - NumBytes = 0; - auto NumToWrite = _bytesRead; - for (Tmp = _backupLimit; - NumToWrite && _userBufferSize / sizeof(WCHAR) > NumBytes; - Tmp++, NumToWrite -= sizeof(WCHAR)) - { - NumBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1; - } - } - - numBytes = _bytesRead; - - if (numBytes > _userBufferSize) - { - return STATUS_BUFFER_OVERFLOW; - } - - memmove(_userBuffer, _backupLimit, numBytes); - } + numBytes = _userBufferSize - writer.size(); controlKeyState = _controlKeyState; - - if (!isUnicode) - { - // if ansi, translate string. - std::unique_ptr tempBuffer; - try - { - tempBuffer = std::make_unique(NumBytes); - } - catch (...) - { - return STATUS_NO_MEMORY; - } - - std::unique_ptr partialEvent; - numBytes = TranslateUnicodeToOem(_userBuffer, - gsl::narrow(numBytes / sizeof(wchar_t)), - tempBuffer.get(), - gsl::narrow(NumBytes), - partialEvent); - - if (partialEvent.get()) - { - GetInputBuffer()->StoreReadPartialByteSequence(std::move(partialEvent)); - } - - if (numBytes > _userBufferSize) - { - return STATUS_BUFFER_OVERFLOW; - } - - memmove(_userBuffer, tempBuffer.get(), numBytes); - if (fAddDbcsLead) - { - numBytes++; - } - } return STATUS_SUCCESS; } void COOKED_READ_DATA::MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) { // See the comment in WaitBlock.cpp for more information. - if (_userBuffer == reinterpret_cast(oldBuffer)) + if (_userBuffer == oldBuffer) { - _userBuffer = reinterpret_cast(newBuffer); + _userBuffer = static_cast(newBuffer); } } diff --git a/src/host/readDataCooked.hpp b/src/host/readDataCooked.hpp index 23096e42b51..a263303e45d 100644 --- a/src/host/readDataCooked.hpp +++ b/src/host/readDataCooked.hpp @@ -37,7 +37,7 @@ class COOKED_READ_DATA final : public ReadData _In_ INPUT_READ_HANDLE_DATA* const pInputReadHandleData, SCREEN_INFORMATION& screenInfo, _In_ size_t UserBufferSize, - _In_ PWCHAR UserBuffer, + _In_ char* UserBuffer, _In_ ULONG CtrlWakeupMask, _In_ const std::wstring_view exeName, _In_ const std::string_view initialData, @@ -129,7 +129,7 @@ class COOKED_READ_DATA final : public ReadData wchar_t* _backupLimit; size_t _userBufferSize; // doubled size in ansi case - wchar_t* _userBuffer; + char* _userBuffer; size_t* _pdwNumBytes; diff --git a/src/host/readDataDirect.cpp b/src/host/readDataDirect.cpp index 21045a9ff4a..c4f58cf3c64 100644 --- a/src/host/readDataDirect.cpp +++ b/src/host/readDataDirect.cpp @@ -10,9 +10,7 @@ // Routine Description: // - Constructs direct read data class to hold context across sessions -// generally when there's not enough data to return. Also used a bit -// internally to just pass some information along the stack -// (regardless of wait necessity). +// generally when there's not enough data to return. // Arguments: // - pInputBuffer - Buffer that data will be read from. // - pInputReadHandleData - Context stored across calls from the same @@ -27,17 +25,10 @@ DirectReadData::DirectReadData(_In_ InputBuffer* const pInputBuffer, const size_t eventReadCount, _In_ std::deque> partialEvents) : ReadData(pInputBuffer, pInputReadHandleData), - _eventReadCount{ eventReadCount }, - _partialEvents{ std::move(partialEvents) }, - _outEvents{} + _eventReadCount{ eventReadCount } { } -// Routine Description: -// - Destructs a read data class. -// - Decrements count of readers waiting on the given handle. -DirectReadData::~DirectReadData() = default; - // Routine Description: // - This routine is called to complete a direct read that blocked in // ReadInputBuffer. The context of the read was saved in the DirectReadData @@ -68,18 +59,17 @@ bool DirectReadData::Notify(const WaitTerminationReason TerminationReason, _Out_ size_t* const pNumBytes, _Out_ DWORD* const pControlKeyState, _Out_ void* const pOutputData) +try { FAIL_FAST_IF_NULL(pOutputData); - FAIL_FAST_IF(_pInputReadHandleData->GetReadCount() == 0); - const auto& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation(); - FAIL_FAST_IF(!gci.IsConsoleLocked()); + assert(Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked()); *pReplyStatus = STATUS_SUCCESS; *pControlKeyState = 0; *pNumBytes = 0; - auto retVal = true; + std::deque> readEvents; // If ctrl-c or ctrl-break was seen, ignore it. @@ -88,105 +78,58 @@ bool DirectReadData::Notify(const WaitTerminationReason TerminationReason, return false; } - // check if a partial byte is already stored that we should read - if (!fIsUnicode && - _pInputBuffer->IsReadPartialByteSequenceAvailable() && - _eventReadCount == 1) - { - _partialEvents.push_back(_pInputBuffer->FetchReadPartialByteSequence(false)); - } - // See if called by CsrDestroyProcess or CsrDestroyThread // via ConsoleNotifyWaitBlock. If so, just decrement the ReadCount and return. if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::ThreadDying)) { *pReplyStatus = STATUS_THREAD_IS_TERMINATING; + return true; } + // We must see if we were woken up because the handle is being // closed. If so, we decrement the read count. If it goes to // zero, we wake up the close thread. Otherwise, we wake up any // other thread waiting for data. - else if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::HandleClosing)) + if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::HandleClosing)) { *pReplyStatus = STATUS_ALERTED; + return true; } - else + + // if we get to here, this routine was called either by the input + // thread or a write routine. both of these callers grab the + // current console lock. + + size_t amountToRead; + if (FAILED(SizeTSub(_eventReadCount, _outEvents.size(), &amountToRead))) { - // if we get to here, this routine was called either by the input - // thread or a write routine. both of these callers grab the - // current console lock. - - // calculate how many events we need to read - size_t amountAlreadyRead; - if (FAILED(SizeTAdd(_partialEvents.size(), _outEvents.size(), &amountAlreadyRead))) - { - *pReplyStatus = STATUS_INTEGER_OVERFLOW; - return retVal; - } - size_t amountToRead; - if (FAILED(SizeTSub(_eventReadCount, amountAlreadyRead, &amountToRead))) - { - *pReplyStatus = STATUS_INTEGER_OVERFLOW; - return retVal; - } - - *pReplyStatus = _pInputBuffer->Read(readEvents, - amountToRead, - false, - false, - fIsUnicode, - false); - - if (*pReplyStatus == CONSOLE_STATUS_WAIT) - { - retVal = false; - } + *pReplyStatus = STATUS_INTEGER_OVERFLOW; + return true; } - if (*pReplyStatus != CONSOLE_STATUS_WAIT) + *pReplyStatus = _pInputBuffer->Read(_outEvents, + amountToRead, + false, + false, + fIsUnicode, + false); + + if (*pReplyStatus == CONSOLE_STATUS_WAIT) { - // split key events to oem chars if necessary - if (*pReplyStatus == STATUS_SUCCESS && !fIsUnicode) - { - try - { - SplitToOem(readEvents); - } - CATCH_LOG(); - } - - // combine partial and whole events - while (!_partialEvents.empty()) - { - readEvents.push_front(std::move(_partialEvents.back())); - _partialEvents.pop_back(); - } - - // move read events to out storage - for (size_t i = 0; i < _eventReadCount; ++i) - { - if (readEvents.empty()) - { - break; - } - _outEvents.push_back(std::move(readEvents.front())); - readEvents.pop_front(); - } - - // store partial event if necessary - if (!readEvents.empty()) - { - _pInputBuffer->StoreReadPartialByteSequence(std::move(readEvents.front())); - readEvents.pop_front(); - FAIL_FAST_IF(!(readEvents.empty())); - } - - // move events to pOutputData - const auto pOutputDeque = reinterpret_cast>* const>(pOutputData); - *pNumBytes = _outEvents.size() * sizeof(INPUT_RECORD); - pOutputDeque->swap(_outEvents); + return false; } - return retVal; + + // move events to pOutputData + const auto pOutputDeque = static_cast>* const>(pOutputData); + *pNumBytes = _outEvents.size() * sizeof(INPUT_RECORD); + *pOutputDeque = std::move(_outEvents); + + return true; +} +catch (...) +{ + *pReplyStatus = wil::StatusFromCaughtException(); + return true; } void DirectReadData::MigrateUserBuffersOnTransitionToBackgroundWait(const void* /*oldBuffer*/, void* /*newBuffer*/) diff --git a/src/host/readDataDirect.hpp b/src/host/readDataDirect.hpp index d1ad2e50334..820ddd59819 100644 --- a/src/host/readDataDirect.hpp +++ b/src/host/readDataDirect.hpp @@ -38,8 +38,6 @@ class DirectReadData final : public ReadData DirectReadData(DirectReadData&&) = default; - ~DirectReadData() override; - void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) override; bool Notify(const WaitTerminationReason TerminationReason, const bool fIsUnicode, @@ -50,6 +48,5 @@ class DirectReadData final : public ReadData private: const size_t _eventReadCount; - std::deque> _partialEvents; std::deque> _outEvents; }; diff --git a/src/host/readDataRaw.cpp b/src/host/readDataRaw.cpp index 56fb91a28f9..9351382c074 100644 --- a/src/host/readDataRaw.cpp +++ b/src/host/readDataRaw.cpp @@ -31,7 +31,6 @@ RAW_READ_DATA::RAW_READ_DATA(_In_ InputBuffer* const pInputBuffer, _BufferSize{ BufferSize }, _BufPtr{ THROW_HR_IF_NULL(E_INVALIDARG, BufPtr) } { - THROW_HR_IF(E_INVALIDARG, _BufferSize % sizeof(wchar_t) != 0); THROW_HR_IF(E_INVALIDARG, _BufferSize == 0); } @@ -82,146 +81,50 @@ bool RAW_READ_DATA::Notify(const WaitTerminationReason TerminationReason, *pControlKeyState = 0; *pNumBytes = 0; - size_t NumBytes = 0; - - PWCHAR lpBuffer; - auto RetVal = true; - auto fAddDbcsLead = false; - auto fSkipFinally = false; // If a ctrl-c is seen, don't terminate read. If ctrl-break is seen, terminate read. if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::CtrlC)) { return false; } - else if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::CtrlBreak)) + + if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::CtrlBreak)) { *pReplyStatus = STATUS_ALERTED; + return true; } + // See if we were called because the thread that owns this wait block is exiting. - else if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::ThreadDying)) + if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::ThreadDying)) { *pReplyStatus = STATUS_THREAD_IS_TERMINATING; + return true; } + // We must see if we were woken up because the handle is being // closed. If so, we decrement the read count. If it goes to zero, // we wake up the close thread. Otherwise, we wake up any other // thread waiting for data. - else if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::HandleClosing)) + if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::HandleClosing)) { *pReplyStatus = STATUS_ALERTED; - } - else - { - // If we get to here, this routine was called either by the input - // thread or a write routine. Both of these callers grab the current - // console lock. - - lpBuffer = _BufPtr; - - if (!fIsUnicode && _pInputBuffer->IsReadPartialByteSequenceAvailable()) - { - auto event = _pInputBuffer->FetchReadPartialByteSequence(false); - const auto pKeyEvent = static_cast(event.get()); - *lpBuffer = static_cast(pKeyEvent->GetCharData()); - _BufferSize -= sizeof(wchar_t); - *pReplyStatus = STATUS_SUCCESS; - fAddDbcsLead = true; - - if (_BufferSize == 0) - { - *pNumBytes = 1; - RetVal = false; - fSkipFinally = true; - } - } - else - { - // This call to GetChar may block. - *pReplyStatus = GetChar(_pInputBuffer, - lpBuffer, - true, - nullptr, - nullptr, - nullptr); - } - - if (FAILED_NTSTATUS(*pReplyStatus) || fSkipFinally) - { - if (*pReplyStatus == CONSOLE_STATUS_WAIT) - { - RetVal = false; - } - } - else - { - NumBytes += IsGlyphFullWidth(*lpBuffer) ? 2 : 1; - lpBuffer++; - *pNumBytes += sizeof(WCHAR); - while (*pNumBytes < _BufferSize) - { - // This call to GetChar won't block. - *pReplyStatus = GetChar(_pInputBuffer, - lpBuffer, - false, - nullptr, - nullptr, - nullptr); - if (FAILED_NTSTATUS(*pReplyStatus)) - { - *pReplyStatus = STATUS_SUCCESS; - break; - } - NumBytes += IsGlyphFullWidth(*lpBuffer) ? 2 : 1; - lpBuffer++; - *pNumBytes += sizeof(WCHAR); - } - } + return true; } - // If the read was completed (status != wait), free the raw read data. - if (*pReplyStatus != CONSOLE_STATUS_WAIT && - !fSkipFinally && - !fIsUnicode) - { - // It's ansi, so translate the string. - std::unique_ptr tempBuffer; - try - { - tempBuffer = std::make_unique(NumBytes); - } - catch (...) - { - return true; - } + // If we get to here, this routine was called either by the input + // thread or a write routine. Both of these callers grab the current + // console lock. - lpBuffer = _BufPtr; - std::unique_ptr partialEvent; - - *pNumBytes = TranslateUnicodeToOem(lpBuffer, - gsl::narrow(*pNumBytes / sizeof(wchar_t)), - tempBuffer.get(), - gsl::narrow(NumBytes), - partialEvent); - if (partialEvent.get()) - { - _pInputBuffer->StoreReadPartialByteSequence(std::move(partialEvent)); - } - - memmove(lpBuffer, tempBuffer.get(), *pNumBytes); - if (fAddDbcsLead) - { - (*pNumBytes)++; - } - } - return RetVal; + std::span buffer{ reinterpret_cast(_BufPtr), _BufferSize }; + *pReplyStatus = ReadCharacterInput(*_pInputBuffer, buffer, *pNumBytes, *_pInputReadHandleData, fIsUnicode); + return *pReplyStatus != CONSOLE_STATUS_WAIT; } void RAW_READ_DATA::MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) { // See the comment in WaitBlock.cpp for more information. - if (_BufPtr == reinterpret_cast(oldBuffer)) + if (_BufPtr == static_cast(oldBuffer)) { - _BufPtr = reinterpret_cast(newBuffer); + _BufPtr = static_cast(newBuffer); } } diff --git a/src/host/stream.cpp b/src/host/stream.cpp index 8df36f82f52..92c76b89ad0 100644 --- a/src/host/stream.cpp +++ b/src/host/stream.cpp @@ -6,7 +6,6 @@ #include "_stream.h" #include "stream.h" -#include "dbcs.h" #include "handle.h" #include "misc.h" #include "readDataRaw.hpp" @@ -285,151 +284,38 @@ til::CoordType RetrieveNumberOfSpaces(_In_ til::CoordType sOriginalCursorPositio size_t& bytesRead, INPUT_READ_HANDLE_DATA& readHandleState, const bool unicode) +try { - // TODO: MSFT: 18047766 - Correct this method to not play byte counting games. - auto fAddDbcsLead = FALSE; - size_t NumToWrite = 0; - size_t NumToBytes = 0; - auto pBuffer = reinterpret_cast(buffer.data()); - auto bufferRemaining = buffer.size_bytes(); bytesRead = 0; - if (buffer.size_bytes() < sizeof(wchar_t)) - { - return STATUS_BUFFER_TOO_SMALL; - } - - const auto pending = readHandleState.GetPendingInput(); - auto pendingBytes = pending.size() * sizeof(wchar_t); - auto Tmp = pending.cbegin(); + auto pending = readHandleState.GetPendingInput(); if (readHandleState.IsMultilineInput()) { - if (!unicode) + const auto idx = pending.find(UNICODE_LINEFEED); + if (idx != decltype(pending)::npos) { - if (inputBuffer.IsReadPartialByteSequenceAvailable()) - { - auto event = inputBuffer.FetchReadPartialByteSequence(false); - const auto pKeyEvent = static_cast(event.get()); - *pBuffer = static_cast(pKeyEvent->GetCharData()); - ++pBuffer; - bufferRemaining -= sizeof(wchar_t); - pendingBytes -= sizeof(wchar_t); - fAddDbcsLead = TRUE; - } - - if (pendingBytes == 0 || bufferRemaining == 0) - { - readHandleState.CompletePending(); - bytesRead = 1; - return STATUS_SUCCESS; - } - else - { - for (NumToWrite = 0, Tmp = pending.cbegin(), NumToBytes = 0; - NumToBytes < pendingBytes && - NumToBytes < bufferRemaining / sizeof(wchar_t) && - *Tmp != UNICODE_LINEFEED; - Tmp++, NumToWrite += sizeof(wchar_t)) - { - NumToBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1; - } - } - } - - NumToWrite = 0; - Tmp = pending.cbegin(); - while (NumToWrite < pendingBytes && - *Tmp != UNICODE_LINEFEED) - { - ++Tmp; - NumToWrite += sizeof(wchar_t); - } - - NumToWrite += sizeof(wchar_t); - if (NumToWrite > bufferRemaining) - { - NumToWrite = bufferRemaining; + // +1 to include the newline. + pending = pending.substr(0, idx + 1); } } - else - { - if (!unicode) - { - if (inputBuffer.IsReadPartialByteSequenceAvailable()) - { - auto event = inputBuffer.FetchReadPartialByteSequence(false); - const auto pKeyEvent = static_cast(event.get()); - *pBuffer = static_cast(pKeyEvent->GetCharData()); - ++pBuffer; - bufferRemaining -= sizeof(wchar_t); - pendingBytes -= sizeof(wchar_t); - fAddDbcsLead = TRUE; - } - - if (pendingBytes == 0) - { - readHandleState.CompletePending(); - bytesRead = 1; - return STATUS_SUCCESS; - } - else - { - for (NumToWrite = 0, Tmp = pending.cbegin(), NumToBytes = 0; - NumToBytes < pendingBytes && NumToBytes < bufferRemaining / sizeof(wchar_t); - Tmp++, NumToWrite += sizeof(wchar_t)) - { - NumToBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1; - } - } - } - NumToWrite = (bufferRemaining < pendingBytes) ? bufferRemaining : pendingBytes; - } + std::span writer{ buffer }; + inputBuffer.Consume(unicode, pending, writer); - memmove(pBuffer, pending.data(), NumToWrite); - pendingBytes -= NumToWrite; - if (pendingBytes != 0) - { - std::wstring_view remainingPending{ pending.data() + (NumToWrite / sizeof(wchar_t)), pendingBytes / sizeof(wchar_t) }; - readHandleState.UpdatePending(remainingPending); - } - else + if (pending.empty()) { readHandleState.CompletePending(); } - - if (!unicode) + else { - // if ansi, translate string. we allocated the capture buffer - // large enough to handle the translated string. - auto tempBuffer = std::make_unique(NumToBytes); - std::unique_ptr partialEvent; - - NumToWrite = TranslateUnicodeToOem(pBuffer, - gsl::narrow(NumToWrite / sizeof(wchar_t)), - tempBuffer.get(), - gsl::narrow(NumToBytes), - partialEvent); - if (partialEvent.get()) - { - inputBuffer.StoreReadPartialByteSequence(std::move(partialEvent)); - } - - // clang-format off -#pragma prefast(suppress: __WARNING_POTENTIAL_BUFFER_OVERFLOW_HIGH_PRIORITY, "This access is fine but prefast can't follow it, evidently") - // clang-format on - memmove(pBuffer, tempBuffer.get(), NumToWrite); - - if (fAddDbcsLead) - { - NumToWrite++; - } + readHandleState.UpdatePending(pending); } - bytesRead = NumToWrite; + bytesRead = buffer.size() - writer.size(); return STATUS_SUCCESS; } +NT_CATCH_RETURN() // Routine Description: // - read in characters until the buffer is full or return is read. @@ -478,7 +364,7 @@ til::CoordType RetrieveNumberOfSpaces(_In_ til::CoordType sOriginalCursorPositio &readHandleState, // pInputReadHandleData screenInfo, // pScreenInfo buffer.size_bytes(), // UserBufferSize - reinterpret_cast(buffer.data()), // UserBuffer + buffer.data(), // UserBuffer ctrlWakeupMask, // CtrlWakeupMask exeName, // exe name initialData, @@ -521,140 +407,51 @@ til::CoordType RetrieveNumberOfSpaces(_In_ til::CoordType sOriginalCursorPositio // populated. // - STATUS_SUCCESS on success // - Other NTSTATUS codes as necessary -[[nodiscard]] static NTSTATUS _ReadCharacterInput(InputBuffer& inputBuffer, - std::span buffer, - size_t& bytesRead, - INPUT_READ_HANDLE_DATA& readHandleState, - const bool unicode, - std::unique_ptr& waiter) +[[nodiscard]] NTSTATUS ReadCharacterInput(InputBuffer& inputBuffer, + std::span buffer, + size_t& bytesRead, + INPUT_READ_HANDLE_DATA& readHandleState, + const bool unicode) +try { - size_t NumToWrite = 0; - auto addDbcsLead = false; - auto Status = STATUS_SUCCESS; - auto pBuffer = reinterpret_cast(buffer.data()); - auto bufferRemaining = buffer.size_bytes(); + UNREFERENCED_PARAMETER(readHandleState); + bytesRead = 0; - if (buffer.size() < 1) + const auto charSize = unicode ? sizeof(wchar_t) : sizeof(char); + std::span writer{ buffer }; + + if (writer.size() < charSize) { return STATUS_BUFFER_TOO_SMALL; } - if (bytesRead < bufferRemaining) - { - auto pwchBufferTmp = pBuffer; - - NumToWrite = 0; + inputBuffer.ConsumeCached(unicode, writer); - if (!unicode && inputBuffer.IsReadPartialByteSequenceAvailable()) - { - auto event = inputBuffer.FetchReadPartialByteSequence(false); - const auto pKeyEvent = static_cast(event.get()); - *pBuffer = static_cast(pKeyEvent->GetCharData()); - ++pBuffer; - bufferRemaining -= sizeof(wchar_t); - addDbcsLead = true; - - if (bufferRemaining == 0) - { - bytesRead = 1; - return STATUS_SUCCESS; - } - } - else - { - Status = GetChar(&inputBuffer, - pBuffer, - true, - nullptr, - nullptr, - nullptr); - } + // We don't need to wait for input if `ConsumeCached` read something already, which is + // indicated by the writer having been advanced (= it's shorter than the original buffer). + auto wait = writer.size() == buffer.size(); + auto status = STATUS_SUCCESS; - if (Status == CONSOLE_STATUS_WAIT) - { - waiter = std::make_unique(&inputBuffer, - &readHandleState, - gsl::narrow(buffer.size_bytes()), - reinterpret_cast(buffer.data())); - } - - if (FAILED_NTSTATUS(Status)) - { - bytesRead = 0; - return Status; - } - - if (!addDbcsLead) - { - bytesRead += IsGlyphFullWidth(*pBuffer) ? 2 : 1; - NumToWrite += sizeof(wchar_t); - pBuffer++; - } - - while (NumToWrite < static_cast(bufferRemaining)) + while (writer.size() >= charSize) + { + wchar_t wch; + status = GetChar(&inputBuffer, &wch, wait, nullptr, nullptr, nullptr); + if (!NT_SUCCESS(status)) { - Status = GetChar(&inputBuffer, - pBuffer, - false, - nullptr, - nullptr, - nullptr); - if (FAILED_NTSTATUS(Status)) - { - break; - } - bytesRead += IsGlyphFullWidth(*pBuffer) ? 2 : 1; - NumToWrite += sizeof(wchar_t); - pBuffer++; + break; } - // if ansi, translate string. we allocated the capture buffer large enough to handle the translated string. - if (!unicode) - { - std::unique_ptr tempBuffer; - try - { - tempBuffer = std::make_unique(bytesRead); - } - catch (...) - { - return STATUS_NO_MEMORY; - } - - pBuffer = pwchBufferTmp; - std::unique_ptr partialEvent; - - bytesRead = TranslateUnicodeToOem(pBuffer, - gsl::narrow(NumToWrite / sizeof(wchar_t)), - tempBuffer.get(), - gsl::narrow(bytesRead), - partialEvent); - - if (partialEvent.get()) - { - inputBuffer.StoreReadPartialByteSequence(std::move(partialEvent)); - } + std::wstring_view wchView{ &wch, 1 }; + inputBuffer.Consume(unicode, wchView, writer); -#pragma prefast(suppress : 26053 26015, "PREfast claims read overflow. *pReadByteCount is the exact size of tempBuffer as allocated above.") - memmove(pBuffer, tempBuffer.get(), bytesRead); - - if (addDbcsLead) - { - ++bytesRead; - } - } - else - { - // We always return the byte count for A & W modes, so in - // the Unicode case where we didn't translate back, set - // the return to the byte count that we assembled while - // pulling characters from the internal buffers. - bytesRead = NumToWrite; - } + wait = false; } - return STATUS_SUCCESS; + + bytesRead = buffer.size() - writer.size(); + return status; } +NT_CATCH_RETURN() // Routine Description: // - This routine reads in characters for stream input and does the @@ -730,12 +527,16 @@ til::CoordType RetrieveNumberOfSpaces(_In_ til::CoordType sOriginalCursorPositio } else { - return _ReadCharacterInput(inputBuffer, - buffer, - bytesRead, - readHandleState, - unicode, - waiter); + const auto status = ReadCharacterInput(inputBuffer, + buffer, + bytesRead, + readHandleState, + unicode); + if (status == CONSOLE_STATUS_WAIT) + { + waiter = std::make_unique(&inputBuffer, &readHandleState, gsl::narrow(buffer.size()), reinterpret_cast(buffer.data())); + } + return status; } } CATCH_RETURN(); diff --git a/src/host/stream.h b/src/host/stream.h index 7ecc0595f23..b4c37d82a45 100644 --- a/src/host/stream.h +++ b/src/host/stream.h @@ -29,6 +29,12 @@ Revision History: _Out_opt_ bool* const pPopupKeys, _Out_opt_ DWORD* const pdwKeyState) noexcept; +[[nodiscard]] NTSTATUS ReadCharacterInput(InputBuffer& inputBuffer, + std::span buffer, + size_t& bytesRead, + INPUT_READ_HANDLE_DATA& readHandleState, + const bool unicode); + // Routine Description: // - This routine returns the total number of screen spaces the characters up to the specified character take up. til::CoordType RetrieveTotalNumberOfSpaces(const til::CoordType sOriginalCursorPositionX, diff --git a/src/host/ut_host/CommandLineTests.cpp b/src/host/ut_host/CommandLineTests.cpp index 3248ee4d298..b003c37b5f2 100644 --- a/src/host/ut_host/CommandLineTests.cpp +++ b/src/host/ut_host/CommandLineTests.cpp @@ -77,7 +77,7 @@ class CommandLineTests const size_t cchBuffer) { cookedReadData._commandHistory = pHistory; - cookedReadData._userBuffer = pBuffer; + cookedReadData._userBuffer = reinterpret_cast(pBuffer); cookedReadData._userBufferSize = cchBuffer * sizeof(wchar_t); cookedReadData._bufferSize = cchBuffer * sizeof(wchar_t); cookedReadData._backupLimit = pBuffer; diff --git a/src/host/ut_host/Host.UnitTests.vcxproj b/src/host/ut_host/Host.UnitTests.vcxproj index a20cf4d5ac0..31f0f8e5a95 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj +++ b/src/host/ut_host/Host.UnitTests.vcxproj @@ -35,7 +35,6 @@ - diff --git a/src/host/ut_host/Host.UnitTests.vcxproj.filters b/src/host/ut_host/Host.UnitTests.vcxproj.filters index 6e56f956f9f..2dcabcef1d4 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj.filters +++ b/src/host/ut_host/Host.UnitTests.vcxproj.filters @@ -54,9 +54,6 @@ Source Files - - Source Files - Source Files diff --git a/src/host/ut_host/InputBufferTests.cpp b/src/host/ut_host/InputBufferTests.cpp index 2c72bc36c60..904ca21f8ca 100644 --- a/src/host/ut_host/InputBufferTests.cpp +++ b/src/host/ut_host/InputBufferTests.cpp @@ -367,7 +367,7 @@ class InputBufferTests TEST_METHOD(EmptyingBufferDuringReadSetsResetWaitEvent) { - Log::Comment(L"ResetWaitEvent should be true if a read to the buffer completely empties it"); + Log::Comment(L"hInputEvent should be reset if a read to the buffer completely empties it"); InputBuffer inputBuffer; @@ -381,48 +381,62 @@ class InputBufferTests } VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u); - // read one record, make sure ResetWaitEvent isn't set + auto& waitEvent = ServiceLocator::LocateGlobals().hInputEvent; + waitEvent.SetEvent(); + + // read one record, hInputEvent should still be signaled std::deque> outEvents; - size_t eventsRead = 0; - auto resetWaitEvent = false; - inputBuffer._ReadBuffer(outEvents, - 1, - eventsRead, - false, - resetWaitEvent, - true, - false); - VERIFY_ARE_EQUAL(eventsRead, 1u); - VERIFY_IS_FALSE(!!resetWaitEvent); - - // read the rest, resetWaitEvent should be set to true + VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents, + 1, + false, + false, + true, + false)); + VERIFY_ARE_EQUAL(outEvents.size(), 1u); + VERIFY_IS_TRUE(waitEvent.is_signaled()); + + // read the rest, hInputEvent should be reset + waitEvent.SetEvent(); outEvents.clear(); - inputBuffer._ReadBuffer(outEvents, - RECORD_INSERT_COUNT - 1, - eventsRead, - false, - resetWaitEvent, - true, - false); - VERIFY_ARE_EQUAL(eventsRead, RECORD_INSERT_COUNT - 1); - VERIFY_IS_TRUE(!!resetWaitEvent); + VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents, + RECORD_INSERT_COUNT - 1, + false, + false, + true, + false)); + VERIFY_ARE_EQUAL(outEvents.size(), RECORD_INSERT_COUNT - 1); + VERIFY_IS_FALSE(waitEvent.is_signaled()); } TEST_METHOD(ReadingDbcsCharsPadsOutputArray) { Log::Comment(L"During a non-unicode read, the input buffer should count twice for each dbcs key event"); + auto& codepage = ServiceLocator::LocateGlobals().getConsoleInformation().CP; + const auto restoreCP = wil::scope_exit([&, previous = codepage]() { + codepage = previous; + }); + + codepage = CP_JAPANESE; + // write a mouse event, key event, dbcs key event, mouse event InputBuffer inputBuffer; - const unsigned int recordInsertCount = 4; - INPUT_RECORD inRecords[recordInsertCount]; + + std::array inRecords{}; inRecords[0].EventType = MOUSE_EVENT; inRecords[1] = MakeKeyEvent(TRUE, 1, L'A', 0, L'A', 0); inRecords[2] = MakeKeyEvent(TRUE, 1, 0x3042, 0, 0x3042, 0); // U+3042 hiragana A inRecords[3].EventType = MOUSE_EVENT; + std::array outRecordsExpected{}; + outRecordsExpected[0].EventType = MOUSE_EVENT; + outRecordsExpected[1] = MakeKeyEvent(TRUE, 1, L'A', 0, L'A', 0); + outRecordsExpected[2] = MakeKeyEvent(TRUE, 1, 0x3042, 0, 0x82, 0); + outRecordsExpected[3] = MakeKeyEvent(TRUE, 1, 0x3042, 0, 0xa0, 0); + outRecordsExpected[4].EventType = MOUSE_EVENT; + std::deque> inEvents; - for (size_t i = 0; i < recordInsertCount; ++i) + for (size_t i = 0; i < inRecords.size(); ++i) { inEvents.push_back(IInputEvent::Create(inRecords[i])); } @@ -432,22 +446,16 @@ class InputBufferTests // read them out non-unicode style and compare std::deque> outEvents; - size_t eventsRead = 0; - auto resetWaitEvent = false; - inputBuffer._ReadBuffer(outEvents, - recordInsertCount, - eventsRead, - false, - resetWaitEvent, - false, - false); - // the dbcs record should have counted for two elements in - // the array, making it so that we get less events read - VERIFY_ARE_EQUAL(eventsRead, recordInsertCount - 1); - VERIFY_ARE_EQUAL(eventsRead, outEvents.size()); - for (size_t i = 0; i < eventsRead; ++i) + VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents, + outRecordsExpected.size(), + false, + false, + false, + false)); + VERIFY_ARE_EQUAL(outEvents.size(), outRecordsExpected.size()); + for (size_t i = 0; i < outEvents.size(); ++i) { - VERIFY_ARE_EQUAL(outEvents[i]->ToInputRecord(), inRecords[i]); + VERIFY_ARE_EQUAL(outEvents[i]->ToInputRecord(), outRecordsExpected[i]); } } diff --git a/src/host/ut_host/ReadWaitTests.cpp b/src/host/ut_host/ReadWaitTests.cpp deleted file mode 100644 index 44c76579e14..00000000000 --- a/src/host/ut_host/ReadWaitTests.cpp +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "misc.h" -#include "dbcs.h" -#include "../../types/inc/IInputEvent.hpp" - -#include "../interactivity/inc/ServiceLocator.hpp" - -#include -#include - -using namespace WEX::Logging; -using Microsoft::Console::Interactivity::ServiceLocator; - -class InputRecordConversionTests -{ - TEST_CLASS(InputRecordConversionTests); - - static const size_t INPUT_RECORD_COUNT = 10; - UINT savedCodepage = 0; - - TEST_CLASS_SETUP(ClassSetup) - { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - savedCodepage = gci.CP; - gci.CP = CP_JAPANESE; - VERIFY_IS_TRUE(!!GetCPInfo(gci.CP, &gci.CPInfo)); - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.CP = savedCodepage; - VERIFY_IS_TRUE(!!GetCPInfo(gci.CP, &gci.CPInfo)); - return true; - } - - TEST_METHOD(SplitToOemLeavesNonKeyEventsAlone) - { - Log::Comment(L"nothing should happen to input events that aren't key events"); - - std::deque> inEvents; - INPUT_RECORD inRecords[INPUT_RECORD_COUNT] = { 0 }; - for (size_t i = 0; i < INPUT_RECORD_COUNT; ++i) - { - inRecords[i].EventType = MOUSE_EVENT; - inRecords[i].Event.MouseEvent.dwMousePosition.X = static_cast(i); - inRecords[i].Event.MouseEvent.dwMousePosition.Y = static_cast(i * 2); - inEvents.push_back(IInputEvent::Create(inRecords[i])); - } - - SplitToOem(inEvents); - VERIFY_ARE_EQUAL(INPUT_RECORD_COUNT, inEvents.size()); - - for (size_t i = 0; i < INPUT_RECORD_COUNT; ++i) - { - VERIFY_ARE_EQUAL(inRecords[i], inEvents[i]->ToInputRecord()); - } - } - - TEST_METHOD(SplitToOemLeavesNonDbcsCharsAlone) - { - Log::Comment(L"non-dbcs chars shouldn't be split"); - - std::deque> inEvents; - INPUT_RECORD inRecords[INPUT_RECORD_COUNT] = { 0 }; - for (size_t i = 0; i < INPUT_RECORD_COUNT; ++i) - { - inRecords[i].EventType = KEY_EVENT; - inRecords[i].Event.KeyEvent.uChar.UnicodeChar = static_cast(L'a' + i); - inEvents.push_back(IInputEvent::Create(inRecords[i])); - } - - SplitToOem(inEvents); - VERIFY_ARE_EQUAL(INPUT_RECORD_COUNT, inEvents.size()); - - for (size_t i = 0; i < INPUT_RECORD_COUNT; ++i) - { - VERIFY_ARE_EQUAL(inRecords[i], inEvents[i]->ToInputRecord()); - } - } - - TEST_METHOD(SplitToOemSplitsDbcsChars) - { - Log::Comment(L"dbcs chars should be split"); - - const auto codepage = ServiceLocator::LocateGlobals().getConsoleInformation().CP; - - INPUT_RECORD inRecords[INPUT_RECORD_COUNT * 2] = { 0 }; - std::deque> inEvents; - // U+3042 hiragana letter A - wchar_t hiraganaA = 0x3042; - wchar_t inChars[INPUT_RECORD_COUNT]; - for (size_t i = 0; i < INPUT_RECORD_COUNT; ++i) - { - auto currentChar = static_cast(hiraganaA + (i * 2)); - inRecords[i].EventType = KEY_EVENT; - inRecords[i].Event.KeyEvent.uChar.UnicodeChar = currentChar; - inChars[i] = currentChar; - inEvents.push_back(IInputEvent::Create(inRecords[i])); - } - - SplitToOem(inEvents); - VERIFY_ARE_EQUAL(INPUT_RECORD_COUNT * 2, inEvents.size()); - - // create the data to compare the output to - char dbcsChars[INPUT_RECORD_COUNT * 2] = { 0 }; - auto writtenBytes = WideCharToMultiByte(codepage, - 0, - inChars, - INPUT_RECORD_COUNT, - dbcsChars, - INPUT_RECORD_COUNT * 2, - nullptr, - FALSE); - VERIFY_ARE_EQUAL(writtenBytes, static_cast(INPUT_RECORD_COUNT * 2)); - for (size_t i = 0; i < INPUT_RECORD_COUNT * 2; ++i) - { - const auto pKeyEvent = static_cast(inEvents[i].get()); - VERIFY_ARE_EQUAL(static_cast(pKeyEvent->GetCharData()), dbcsChars[i]); - } - } -}; diff --git a/src/inc/til/bytes.h b/src/inc/til/bytes.h new file mode 100644 index 00000000000..593a0cd435f --- /dev/null +++ b/src/inc/til/bytes.h @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "type_traits.h" + +namespace til +{ + template + constexpr void bytes_advance(Target& target, size_t count) + { + if (count > target.size()) + { + throw std::length_error{ "insufficient space left" }; + } + + target = { target.data() + count, target.size() - count }; + } + + template + constexpr bool bytes_can_put(const Target& target) + { + return target.size() >= sizeof(T); + } + + template + constexpr void bytes_put(Target& target, const T& value) + { + using TargetType = typename Target::value_type; + constexpr auto size = sizeof(value); + + if (size > target.size()) + { + throw std::length_error{ "insufficient space left" }; + } + + std::copy_n(reinterpret_cast(&value), size, target.data()); + target = { target.data() + size, target.size() - size }; + } + + template + requires TriviallyCopyable + constexpr void bytes_transfer(Target& target, Source& source) + { + using TargetType = typename Target::value_type; + constexpr auto elementSize = sizeof(typename Source::value_type); + + const auto sourceCount = std::min(source.size(), target.size() / elementSize); + const auto targetCount = sourceCount * elementSize; + + std::copy_n(reinterpret_cast(source.data()), targetCount, target.data()); + + target = { target.data() + targetCount, target.size() - targetCount }; + source = { source.data() + sourceCount, source.size() - sourceCount }; + } +} diff --git a/src/inc/til/type_traits.h b/src/inc/til/type_traits.h new file mode 100644 index 00000000000..a4a10173199 --- /dev/null +++ b/src/inc/til/type_traits.h @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +namespace til +{ + namespace details + { + template + struct is_contiguous_view : std::false_type + { + }; + template + struct is_contiguous_view> : std::true_type + { + }; + template + struct is_contiguous_view> : std::true_type + { + }; + + template + struct is_byte : std::false_type + { + }; + template<> + struct is_byte : std::true_type + { + }; + template<> + struct is_byte : std::true_type + { + }; + } + + template + concept Byte = details::is_byte>::value; + + template + concept ContiguousView = details::is_contiguous_view>::value; + + template + concept ContiguousBytes = ContiguousView && Byte; + + template + concept TriviallyCopyable = std::is_trivially_copyable_v; +} diff --git a/src/server/ApiDispatchers.cpp b/src/server/ApiDispatchers.cpp index 0558b43ac78..6b16a2bde05 100644 --- a/src/server/ApiDispatchers.cpp +++ b/src/server/ApiDispatchers.cpp @@ -270,13 +270,8 @@ // Get output parameter buffer. PVOID pvBuffer; ULONG cbBufferSize; - // TODO: This is dumb. We should find out how much we need, not guess. - // If the request is not in Unicode mode, we must allocate an output buffer that is twice as big as the actual caller buffer. - RETURN_IF_FAILED(m->GetAugmentedOutputBuffer((a->Unicode != FALSE) ? 1 : 2, - &pvBuffer, - &cbBufferSize)); + RETURN_IF_FAILED(m->GetOutputBuffer(&pvBuffer, &cbBufferSize)); - // TODO: This is also rather strange and will also probably make more sense if we stop guessing that we need 2x buffer to convert. // This might need to go on the other side of the fence (inside host) because the server doesn't know what we're going to do with initial num bytes. // (This restriction exists because it's going to copy initial into the final buffer, but we don't know that.) RETURN_HR_IF(E_INVALIDARG, a->InitialNumBytes > cbBufferSize); diff --git a/src/types/inc/IInputEvent.hpp b/src/types/inc/IInputEvent.hpp index edda9846a55..50626cdda7e 100644 --- a/src/types/inc/IInputEvent.hpp +++ b/src/types/inc/IInputEvent.hpp @@ -64,6 +64,8 @@ class IInputEvent inline IInputEvent::~IInputEvent() = default; +using InputEventQueue = std::deque>; + #ifdef UNIT_TESTING std::wostream& operator<<(std::wostream& stream, const IInputEvent* pEvent); #endif