This commit is contained in:
Leonard Hecker 2024-01-15 21:48:31 +01:00
parent a4445ed4cc
commit 6126532e71
24 changed files with 288 additions and 1043 deletions

View File

@ -116,7 +116,7 @@
<WarningLevel>Level4</WarningLevel>
<TreatSpecificWarningsAsErrors>4189;4100;4242;4389;4244</TreatSpecificWarningsAsErrors>
<!--<WarningLevel>EnableAllWarnings</WarningLevel>-->
<TreatWarningAsError>true</TreatWarningAsError>
<!--<TreatWarningAsError>true</TreatWarningAsError>-->
<!--
C4201: nonstandard extension used: nameless struct/union
Conhost code uses a lot of nameless structs/unions.

View File

@ -50,8 +50,8 @@ public:
ULONG& events) noexcept override;
[[nodiscard]] HRESULT GetConsoleInputImpl(IConsoleInputObject& context,
InputEventQueue& outEvents,
const size_t eventReadCount,
INPUT_RECORD* outEvents,
size_t* eventReadCount,
INPUT_READ_HANDLE_DATA& readHandleState,
const bool IsUnicode,
const bool IsPeek,

View File

@ -107,8 +107,8 @@ void VtApiRoutines::_SynchronizeCursor(std::unique_ptr<IWaitRoutine>& waiter) no
[[nodiscard]] HRESULT VtApiRoutines::GetConsoleInputImpl(
IConsoleInputObject& context,
InputEventQueue& outEvents,
const size_t eventReadCount,
INPUT_RECORD* outEvents,
size_t* eventReadCount,
INPUT_READ_HANDLE_DATA& readHandleState,
const bool IsUnicode,
const bool IsPeek,

View File

@ -53,8 +53,8 @@ public:
ULONG& events) noexcept override;
[[nodiscard]] HRESULT GetConsoleInputImpl(IConsoleInputObject& context,
InputEventQueue& outEvents,
const size_t eventReadCount,
INPUT_RECORD* outEvents,
size_t* eventReadCount,
INPUT_READ_HANDLE_DATA& readHandleState,
const bool IsUnicode,
const bool IsPeek,

View File

@ -51,8 +51,8 @@ using Microsoft::Console::Interactivity::ServiceLocator;
// block, this will be returned along with context in *ppWaiter.
// - Or an out of memory/math/string error message in NTSTATUS format.
[[nodiscard]] HRESULT ApiRoutines::GetConsoleInputImpl(IConsoleInputObject& inputBuffer,
InputEventQueue& outEvents,
const size_t eventReadCount,
INPUT_RECORD* outEvents,
size_t* eventReadCount,
INPUT_READ_HANDLE_DATA& readHandleState,
const bool IsUnicode,
const bool IsPeek,
@ -62,64 +62,30 @@ using Microsoft::Console::Interactivity::ServiceLocator;
{
waiter.reset();
if (eventReadCount == 0)
if (*eventReadCount == 0)
{
return STATUS_SUCCESS;
return S_OK;
}
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
const auto Status = inputBuffer.Read(outEvents,
eventReadCount,
IsPeek,
true,
IsUnicode,
false);
if (CONSOLE_STATUS_WAIT == Status)
const InputBuffer::ReadDescriptor readDesc{
.wide = IsUnicode,
.records = true,
.peek = IsPeek,
};
const auto count = inputBuffer.Read(readDesc, outEvents, *eventReadCount * sizeof(INPUT_RECORD));
if (count)
{
// 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<DirectReadData>(&inputBuffer,
&readHandleState,
eventReadCount);
}
return Status;
}
CATCH_RETURN();
}
// Routine Description:
// - Writes events to the input buffer
// Arguments:
// - context - the input buffer to write to
// - events - the events to written
// - written - on output, the number of events written
// - append - true if events should be written to the end of the input
// buffer, false if they should be written to the front
// Return Value:
// - HRESULT indicating success or failure
[[nodiscard]] static HRESULT _WriteConsoleInputWImplHelper(InputBuffer& context,
const std::span<const INPUT_RECORD>& events,
size_t& written,
const bool append) noexcept
{
try
{
written = 0;
// add to InputBuffer
if (append)
{
written = context.Write(events);
}
else
{
written = context.Prepend(events);
*eventReadCount = count / sizeof(INPUT_RECORD);
return S_OK;
}
return S_OK;
// 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<DirectReadData>(&inputBuffer, &readHandleState, *eventReadCount);
return CONSOLE_STATUS_WAIT;
}
CATCH_RETURN();
}
@ -225,7 +191,11 @@ try
}
}
return _WriteConsoleInputWImplHelper(context, events, written, append);
// TODO: append/prepend
UNREFERENCED_PARAMETER(append);
context.Write(events);
written = buffer.size();
return S_OK;
}
CATCH_RETURN();
@ -243,18 +213,20 @@ CATCH_RETURN();
const std::span<const INPUT_RECORD> buffer,
size_t& written,
const bool append) noexcept
try
{
written = 0;
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
try
{
return _WriteConsoleInputWImplHelper(context, buffer, written, append);
}
CATCH_RETURN();
// TODO: append/prepend
UNREFERENCED_PARAMETER(append);
context.Write(buffer);
written = buffer.size();
return S_OK;
}
CATCH_RETURN();
// Routine Description:
// - This is used when the app is reading output as cells and needs them converted

View File

@ -24,12 +24,6 @@ bool IsInProcessedInputMode()
return (gci.pInputBuffer->InputMode & ENABLE_PROCESSED_INPUT) != 0;
}
bool IsInVirtualTerminalInputMode()
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return WI_IsFlagSet(gci.pInputBuffer->InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT);
}
BOOL IsSystemKey(const WORD wVirtualKeyCode)
{
switch (wVirtualKeyCode)
@ -197,23 +191,12 @@ void HandleFocusEvent(const BOOL fSetFocus)
}
void HandleMenuEvent(const DWORD wParam)
try
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
size_t EventsWritten = 0;
try
{
EventsWritten = gci.pInputBuffer->Write(SynthesizeMenuEvent(wParam));
if (EventsWritten != 1)
{
LOG_HR_MSG(E_FAIL, "PutInputInBuffer: EventsWritten != 1, 1 expected");
}
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
}
gci.pInputBuffer->Write(SynthesizeMenuEvent(wParam));
}
CATCH_LOG()
void HandleCtrlEvent(const DWORD EventType)
{

View File

@ -12,7 +12,7 @@
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/GlyphWidth.hpp"
#define INPUT_BUFFER_DEFAULT_INPUT_MODE (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT)
#pragma warning(disable : 4100)
using Microsoft::Console::Interactivity::ServiceLocator;
using Microsoft::Console::VirtualTerminal::TerminalInput;
@ -24,8 +24,7 @@ using namespace Microsoft::Console;
// - None
// Return Value:
// - A new instance of InputBuffer
InputBuffer::InputBuffer() :
InputMode{ INPUT_BUFFER_DEFAULT_INPUT_MODE }
InputBuffer::InputBuffer()
{
// initialize buffer header
fInComposition = false;
@ -168,18 +167,7 @@ void InputBuffer::Cache(std::wstring_view source)
// Moves up to `count`, previously cached events into `target`.
size_t InputBuffer::ConsumeCached(bool isUnicode, size_t count, InputEventQueue& target)
{
_switchReadingMode(isUnicode ? ReadingMode::InputEventsW : ReadingMode::InputEventsA);
size_t i = 0;
while (i < count && !_cachedInputEvents.empty())
{
target.push_back(std::move(_cachedInputEvents.front()));
_cachedInputEvents.pop_front();
i++;
}
return i;
return 0;
}
// Copies up to `count`, previously cached events into `target`.
@ -211,10 +199,6 @@ void InputBuffer::Cache(bool isUnicode, InputEventQueue& source, size_t expected
if (source.size() > expectedSourceSize)
{
_cachedInputEvents.insert(
_cachedInputEvents.end(),
std::make_move_iterator(source.begin() + expectedSourceSize),
std::make_move_iterator(source.end()));
source.resize(expectedSourceSize);
}
}
@ -235,8 +219,6 @@ void InputBuffer::_switchReadingModeSlowPath(ReadingMode mode)
_cachedTextW = std::wstring{};
_cachedTextReaderW = {};
_cachedInputEvents = std::deque<INPUT_RECORD>{};
_readingMode = mode;
}
@ -277,19 +259,6 @@ void InputBuffer::StoreWritePartialByteSequence(const INPUT_RECORD& event) noexc
_writePartialByteSequence = event;
}
// Routine Description:
// - This routine resets the input buffer information fields to their initial values.
// Arguments:
// Return Value:
// Note:
// - The console lock must be held when calling this routine.
void InputBuffer::ReinitializeInputBuffer()
{
ServiceLocator::LocateGlobals().hInputEvent.ResetEvent();
InputMode = INPUT_BUFFER_DEFAULT_INPUT_MODE;
_storage.clear();
}
// Routine Description:
// - Wakes up readers waiting for data to read.
// Arguments:
@ -322,7 +291,7 @@ void InputBuffer::TerminateRead(_In_ WaitTerminationReason Flag)
// - The console lock must be held when calling this routine.
size_t InputBuffer::GetNumberOfReadyEvents() const noexcept
{
return _storage.size();
return 0;
}
// Routine Description:
@ -349,260 +318,197 @@ void InputBuffer::Flush()
// - The console lock must be held when calling this routine.
void InputBuffer::FlushAllButKeys()
{
auto newEnd = std::remove_if(_storage.begin(), _storage.end(), [](const INPUT_RECORD& event) {
return event.EventType != KEY_EVENT;
});
_storage.erase(newEnd, _storage.end());
}
// Routine Description:
// - This routine reads from the input buffer.
// - It can convert returned data to through the currently set Input CP, it can optionally return a wait condition
// if there isn't enough data in the buffer, and it can be set to not remove records as it reads them out.
// Note:
// - The console lock must be held when calling this routine.
// Arguments:
// - OutEvents - deque to store the read events
// - AmountToRead - the amount of events to try to read
// - Peek - If true, copy events to pInputRecord but don't remove them from the input buffer.
// - WaitForData - if true, wait until an event is input (if there aren't enough to fill client buffer). if false, return immediately
// - Unicode - true if the data in key events should be treated as unicode. false if they should be converted by the current input CP.
// - Stream - true if read should unpack KeyEvents that have a >1 repeat count. AmountToRead must be 1 if Stream is true.
// Return Value:
// - STATUS_SUCCESS if records were read into the client buffer and everything is OK.
// - CONSOLE_STATUS_WAIT if there weren't enough records to satisfy the request (and waits are allowed)
// - otherwise a suitable memory/math/string error in NTSTATUS form.
[[nodiscard]] NTSTATUS InputBuffer::Read(_Out_ InputEventQueue& OutEvents,
const size_t AmountToRead,
const bool Peek,
const bool WaitForData,
const bool Unicode,
const bool Stream)
try
static void transfer(InputBuffer::RecordVec& in, std::span<INPUT_RECORD> out)
{
assert(OutEvents.empty());
const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().CP;
if (Peek)
const auto count = std::min(in.size(), out.size());
std::copy_n(in.begin(), count, out.begin());
if (count == out.size())
{
PeekCached(Unicode, AmountToRead, OutEvents);
in.clear();
}
else
{
ConsumeCached(Unicode, AmountToRead, OutEvents);
in.erase(in.begin(), in.begin() + count);
}
}
static void transfer(InputBuffer::RecordVec& in, std::span<wchar_t> out)
{
size_t inUsed = 0;
size_t outUsed = 0;
for (auto& r : in)
{
if (outUsed == out.size())
{
break;
}
if (r.EventType == KEY_EVENT && r.Event.KeyEvent.uChar.UnicodeChar != 0)
{
out[outUsed++] = r.Event.KeyEvent.uChar.UnicodeChar;
}
inUsed++;
}
}
static void transfer(InputBuffer::TextVec& in, std::span<INPUT_RECORD> out)
{
// TODO: MSFT 14150722 - can these const values be generated at
// runtime without breaking compatibility?
static constexpr WORD altScanCode = 0x38;
static constexpr WORD leftShiftScanCode = 0x2A;
size_t inUsed = 0;
size_t outUsed = 0;
for (const auto wch : in)
{
if (outUsed + 4 > out.size())
{
break;
}
const auto keyState = OneCoreSafeVkKeyScanW(wch);
const auto vk = LOBYTE(keyState);
const auto sc = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
// The caller provides us with the result of VkKeyScanW() in keyState.
// The magic constants below are the expected (documented) return values from VkKeyScanW().
const auto modifierState = HIBYTE(keyState);
const auto shiftSet = WI_IsFlagSet(modifierState, 1);
const auto ctrlSet = WI_IsFlagSet(modifierState, 2);
const auto altSet = WI_IsFlagSet(modifierState, 4);
const auto altGrSet = WI_AreAllFlagsSet(modifierState, 4 | 2);
if (altGrSet)
{
out[outUsed++] = SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, 0, ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
}
else if (shiftSet)
{
out[outUsed++] = SynthesizeKeyEvent(true, 1, VK_SHIFT, leftShiftScanCode, 0, SHIFT_PRESSED);
}
auto keyEvent = SynthesizeKeyEvent(true, 1, vk, sc, wch, 0);
WI_SetFlagIf(keyEvent.Event.KeyEvent.dwControlKeyState, SHIFT_PRESSED, shiftSet);
WI_SetFlagIf(keyEvent.Event.KeyEvent.dwControlKeyState, LEFT_CTRL_PRESSED, ctrlSet);
WI_SetFlagIf(keyEvent.Event.KeyEvent.dwControlKeyState, RIGHT_ALT_PRESSED, altSet);
out[outUsed++] = keyEvent;
keyEvent.Event.KeyEvent.bKeyDown = FALSE;
out[outUsed++] = keyEvent;
// handle yucky alt-gr keys
if (altGrSet)
{
out[outUsed++] = SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, 0, ENHANCED_KEY);
}
else if (shiftSet)
{
out[outUsed++] = SynthesizeKeyEvent(false, 1, VK_SHIFT, leftShiftScanCode, 0, 0);
}
inUsed++;
}
}
static void transfer(InputBuffer::TextVec& in, std::span<wchar_t> out)
{
const auto count = std::min(in.size(), out.size());
std::copy_n(in.begin(), count, out.begin());
if (count == out.size())
{
in.clear();
}
else
{
in.erase(in.begin(), in.begin() + count);
}
}
size_t InputBuffer::Read(ReadDescriptor desc, void* data, size_t capacityInBytes)
{
auto remaining = capacityInBytes;
auto it = _storage.begin();
const auto end = _storage.end();
while (it != end && OutEvents.size() < AmountToRead)
for (; it != end; ++it)
{
if (it->EventType == KEY_EVENT)
{
auto event = *it;
WORD repeat = 1;
// for stream reads we need to split any key events that have been coalesced
if (Stream)
{
repeat = std::max<WORD>(1, event.Event.KeyEvent.wRepeatCount);
event.Event.KeyEvent.wRepeatCount = 1;
}
if (Unicode)
{
do
std::visit(
[&]<typename T>(T& arg) {
if constexpr (std::is_same_v<T, RecordVec>)
{
OutEvents.push_back(event);
repeat--;
} while (repeat > 0 && OutEvents.size() < AmountToRead);
}
else
{
const auto wch = event.Event.KeyEvent.uChar.UnicodeChar;
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<size_t>(length) };
do
{
for (const auto& ch : str)
if (desc.records)
{
// char is signed and assigning it to UnicodeChar would cause sign-extension.
// unsigned char doesn't have this problem.
event.Event.KeyEvent.uChar.UnicodeChar = til::bit_cast<uint8_t>(ch);
OutEvents.push_back(event);
transfer(arg, { static_cast<INPUT_RECORD*>(data), capacityInBytes / sizeof(INPUT_RECORD) });
}
repeat--;
} while (repeat > 0 && OutEvents.size() < AmountToRead);
}
if (repeat && !Peek)
{
it->Event.KeyEvent.wRepeatCount = repeat;
break;
}
}
else
{
OutEvents.push_back(*it);
}
++it;
else
{
transfer(arg, { static_cast<wchar_t*>(data), capacityInBytes / sizeof(wchar_t) });
}
}
else if constexpr (std::is_same_v<T, TextVec>)
{
if (desc.records)
{
transfer(arg, { static_cast<INPUT_RECORD*>(data), capacityInBytes / sizeof(INPUT_RECORD) });
}
else
{
transfer(arg, { static_cast<wchar_t*>(data), capacityInBytes / sizeof(wchar_t) });
}
}
else
{
static_assert(sizeof(arg) == 0, "non-exhaustive visitor!");
}
},
*it);
}
if (!Peek)
if (!desc.peek)
{
_storage.erase(_storage.begin(), it);
}
Cache(Unicode, OutEvents, AmountToRead);
if (OutEvents.empty())
{
return WaitForData ? CONSOLE_STATUS_WAIT : STATUS_SUCCESS;
}
if (_storage.empty())
{
ServiceLocator::LocateGlobals().hInputEvent.ResetEvent();
}
return STATUS_SUCCESS;
return capacityInBytes - remaining;
}
catch (...)
void InputBuffer::Write(const INPUT_RECORD& record)
{
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
Write(std::span{ &record, 1 });
}
// Routine Description:
// - Writes events to the beginning of the input buffer.
// Arguments:
// - inEvents - events to write to buffer.
// - eventsWritten - The number of events written to the buffer on exit.
// Return Value:
// S_OK if successful.
// Note:
// - The console lock must be held when calling this routine.
size_t InputBuffer::Prepend(const std::span<const INPUT_RECORD>& inEvents)
void InputBuffer::Write(const std::span<const INPUT_RECORD>& records)
try
{
try
if (records.empty())
{
if (inEvents.empty())
{
return STATUS_SUCCESS;
}
_vtInputShouldSuppress = true;
auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; });
// read all of the records out of the buffer, then write the
// prepend ones, then write the original set. We need to do it
// this way to handle any coalescing that might occur.
// get all of the existing records, "emptying" the buffer
std::deque<INPUT_RECORD> existingStorage;
existingStorage.swap(_storage);
// We will need this variable to pass to _WriteBuffer so it can attempt to determine wait status.
// However, because we swapped the storage out from under it with an empty deque, it will always
// return true after the first one (as it is filling the newly emptied backing deque.)
// Then after the second one, because we've inserted some input, it will always say false.
auto unusedWaitStatus = false;
// write the prepend records
size_t prependEventsWritten;
_WriteBuffer(inEvents, prependEventsWritten, unusedWaitStatus);
FAIL_FAST_IF(!(unusedWaitStatus));
for (const auto& event : existingStorage)
{
_storage.push_back(event);
}
// We need to set the wait event if there were 0 events in the
// input queue when we started.
// Because we did interesting manipulation of the wait queue
// in order to prepend, we can't trust what _WriteBuffer said
// and instead need to set the event if the original backing
// buffer (the one we swapped out at the top) was empty
// when this whole thing started.
if (existingStorage.empty())
{
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
}
WakeUpReadersWaitingForData();
return prependEventsWritten;
return;
}
catch (...)
const auto initiallyEmpty = _storage.empty();
if (initiallyEmpty || _storage.back().index() != 0)
{
LOG_HR(wil::ResultFromCaughtException());
return 0;
_storage.emplace_back(RecordVec{});
}
auto& v = *std::get_if<RecordVec>(&_storage.back());
v.insert(v.end(), records.begin(), records.end());
if (initiallyEmpty)
{
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
}
WakeUpReadersWaitingForData();
}
CATCH_LOG()
// Routine Description:
// - Writes event to the input buffer. Wakes up any readers that are
// waiting for additional input events.
// Arguments:
// - inEvent - input event to store in the buffer.
// Return Value:
// - The number of events that were written to input buffer.
// Note:
// - The console lock must be held when calling this routine.
// - any outside references to inEvent will ben invalidated after
// calling this method.
size_t InputBuffer::Write(const INPUT_RECORD& inEvent)
{
return Write(std::span{ &inEvent, 1 });
}
// Routine Description:
// - Writes events to the input buffer. Wakes up any readers that are
// waiting for additional input events.
// Arguments:
// - inEvents - input events to store in the buffer.
// Return Value:
// - The number of events that were written to input buffer.
// Note:
// - The console lock must be held when calling this routine.
size_t InputBuffer::Write(const std::span<const INPUT_RECORD>& inEvents)
{
try
{
if (inEvents.empty())
{
return 0;
}
_vtInputShouldSuppress = true;
auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; });
// Write to buffer.
size_t EventsWritten;
bool SetWaitEvent;
_WriteBuffer(inEvents, EventsWritten, SetWaitEvent);
if (SetWaitEvent)
{
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
}
// Alert any writers waiting for space.
WakeUpReadersWaitingForData();
return EventsWritten;
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return 0;
}
}
void InputBuffer::WriteString(const std::wstring_view& text)
void InputBuffer::Write(const std::wstring_view& text)
try
{
if (text.empty())
@ -610,15 +516,20 @@ try
return;
}
const auto initiallyEmptyQueue = _storage.empty();
const auto initiallyEmpty = _storage.empty();
_writeString(text);
if (initiallyEmpty || _storage.back().index() != 1)
{
_storage.emplace_back(TextVec{});
}
if (initiallyEmptyQueue && !_storage.empty())
auto& v = *std::get_if<TextVec>(&_storage.back());
v.insert(v.end(), text.begin(), text.end());
if (initiallyEmpty)
{
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
}
WakeUpReadersWaitingForData();
}
CATCH_LOG()
@ -628,55 +539,12 @@ CATCH_LOG()
// input buffer and the next application will suddenly get a "\x1b[I" sequence in their input. See GH#13238.
void InputBuffer::WriteFocusEvent(bool focused) noexcept
{
if (IsInVirtualTerminalInputMode())
{
if (const auto out = _termInput.HandleFocus(focused))
{
_HandleTerminalInputCallback(*out);
}
}
else
{
// This is a mini-version of Write().
const auto wasEmpty = _storage.empty();
_storage.push_back(SynthesizeFocusEvent(focused));
if (wasEmpty)
{
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
}
WakeUpReadersWaitingForData();
}
//Write(SynthesizeFocusEvent(focused));
}
// Returns true when mouse input started. You should then capture the mouse and produce further events.
bool InputBuffer::WriteMouseEvent(til::point position, const unsigned int button, const short keyState, const short wheelDelta)
{
if (IsInVirtualTerminalInputMode())
{
// This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx
// "If the high-order bit is 1, the key is down; otherwise, it is up."
static constexpr short KeyPressed{ gsl::narrow_cast<short>(0x8000) };
const TerminalInput::MouseButtonState state{
WI_IsFlagSet(OneCoreSafeGetKeyState(VK_LBUTTON), KeyPressed),
WI_IsFlagSet(OneCoreSafeGetKeyState(VK_MBUTTON), KeyPressed),
WI_IsFlagSet(OneCoreSafeGetKeyState(VK_RBUTTON), KeyPressed)
};
// GH#6401: VT applications should be able to receive mouse events from outside the
// terminal buffer. This is likely to happen when the user drags the cursor offscreen.
// We shouldn't throw away perfectly good events when they're offscreen, so we just
// clamp them to be within the range [(0, 0), (W, H)].
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetActiveOutputBuffer().GetViewport().ToOrigin().Clamp(position);
if (const auto out = _termInput.HandleMouse(position, button, keyState, wheelDelta, state))
{
_HandleTerminalInputCallback(*out);
return true;
}
}
return false;
}
@ -693,135 +561,6 @@ static bool IsPauseKey(const KEY_EVENT_RECORD& event)
return ctrlButNotAlt && event.wVirtualKeyCode == L'S';
}
// Routine Description:
// - Coalesces input events and transfers them to storage queue.
// Arguments:
// - inRecords - The events to store.
// - eventsWritten - The number of events written since this function
// was called.
// - setWaitEvent - on exit, true if buffer became non-empty.
// Return Value:
// - None
// Note:
// - The console lock must be held when calling this routine.
// - will throw on failure
void InputBuffer::_WriteBuffer(const std::span<const INPUT_RECORD>& inEvents, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
eventsWritten = 0;
setWaitEvent = false;
const auto initiallyEmptyQueue = _storage.empty();
const auto initialInEventsSize = inEvents.size();
const auto vtInputMode = IsInVirtualTerminalInputMode();
for (const auto& inEvent : inEvents)
{
if (inEvent.EventType == KEY_EVENT && inEvent.Event.KeyEvent.bKeyDown)
{
// if output is suspended, any keyboard input releases it.
if (WI_IsFlagSet(gci.Flags, CONSOLE_SUSPENDED) && !IsSystemKey(inEvent.Event.KeyEvent.wVirtualKeyCode))
{
UnblockWriteConsole(CONSOLE_OUTPUT_SUSPENDED);
continue;
}
// intercept control-s
if (WI_IsFlagSet(InputMode, ENABLE_LINE_INPUT) && IsPauseKey(inEvent.Event.KeyEvent))
{
WI_SetFlag(gci.Flags, CONSOLE_SUSPENDED);
continue;
}
}
// If we're in vt mode, try and handle it with the vt input module.
// If it was handled, do nothing else for it.
// If there was one event passed in, try coalescing it with the previous event currently in the buffer.
// If it's not coalesced, append it to the buffer.
if (vtInputMode)
{
// GH#11682: TerminalInput::HandleKey can handle both KeyEvents and Focus events seamlessly
if (const auto out = _termInput.HandleKey(inEvent))
{
_HandleTerminalInputCallback(*out);
eventsWritten++;
continue;
}
}
// we only check for possible coalescing when storing one
// record at a time because this is the original behavior of
// the input buffer. Changing this behavior may break stuff
// that was depending on it.
if (initialInEventsSize == 1 && !_storage.empty() && _CoalesceEvent(inEvents[0]))
{
eventsWritten++;
return;
}
// At this point, the event was neither coalesced, nor processed by VT.
_storage.push_back(inEvent);
++eventsWritten;
}
if (initiallyEmptyQueue && !_storage.empty())
{
setWaitEvent = true;
}
}
// Routine Description::
// - If the last input event saved and the first input event in inRecords
// are both a keypress down event for the same key, update the repeat
// count of the saved event and drop the first from inRecords.
// Arguments:
// - inRecords - The incoming records to process.
// Return Value:
// true if events were coalesced, false if they were not.
// Note:
// - The size of inRecords must be 1.
// - Coalescing here means updating a record that already exists in
// the buffer with updated values from an incoming event, instead of
// storing the incoming event (which would make the original one
// redundant/out of date with the most current state).
bool InputBuffer::_CoalesceEvent(const INPUT_RECORD& inEvent) noexcept
{
auto& lastEvent = _storage.back();
if (lastEvent.EventType == MOUSE_EVENT && inEvent.EventType == MOUSE_EVENT)
{
const auto& inMouse = inEvent.Event.MouseEvent;
auto& lastMouse = lastEvent.Event.MouseEvent;
if (lastMouse.dwEventFlags == MOUSE_MOVED && inMouse.dwEventFlags == MOUSE_MOVED)
{
lastMouse.dwMousePosition = inMouse.dwMousePosition;
return true;
}
}
else if (lastEvent.EventType == KEY_EVENT && inEvent.EventType == KEY_EVENT)
{
const auto& inKey = inEvent.Event.KeyEvent;
auto& lastKey = lastEvent.Event.KeyEvent;
if (lastKey.bKeyDown && inKey.bKeyDown &&
(lastKey.wVirtualScanCode == inKey.wVirtualScanCode || WI_IsFlagSet(inKey.dwControlKeyState, NLS_IME_CONVERSION)) &&
lastKey.uChar.UnicodeChar == inKey.uChar.UnicodeChar &&
lastKey.dwControlKeyState == inKey.dwControlKeyState &&
// TODO:GH#8000 This behavior is an import from old conhost v1 and has been broken for decades.
// This is probably the outdated idea that any wide glyph is being represented by 2 characters (DBCS) and likely
// resulted from conhost originally being split into a ASCII/OEM and a DBCS variant with preprocessor flags.
// You can't update the repeat count of such a A,B pair, because they're stored as A,A,B,B (down-down, up-up).
// I believe the proper approach is to store pairs of characters as pairs, update their combined
// repeat count and only when they're being read de-coalesce them into their alternating form.
!IsGlyphFullWidth(inKey.uChar.UnicodeChar))
{
lastKey.wRepeatCount += inKey.wRepeatCount;
return true;
}
}
return false;
}
// Routine Description:
// - Returns true if this input buffer is in VT Input mode.
// Arguments:
@ -833,55 +572,6 @@ bool InputBuffer::IsInVirtualTerminalInputMode() const
return WI_IsFlagSet(InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT);
}
// Routine Description:
// - Handler for inserting key sequences into the buffer when the terminal emulation layer
// has determined a key can be converted appropriately into a sequence of inputs
// Arguments:
// - inEvents - Series of input records to insert into the buffer
// Return Value:
// - <none>
void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& text)
{
try
{
if (text.empty())
{
return;
}
_writeString(text);
if (!_vtInputShouldSuppress)
{
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
WakeUpReadersWaitingForData();
}
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
}
}
void InputBuffer::_writeString(const std::wstring_view& text)
{
for (const auto& wch : text)
{
if (wch == UNICODE_NULL)
{
// Convert null byte back to input event with proper control state
const auto zeroKey = OneCoreSafeVkKeyScanW(0);
uint32_t ctrlState = 0;
WI_SetFlagIf(ctrlState, SHIFT_PRESSED, WI_IsFlagSet(zeroKey, 0x100));
WI_SetFlagIf(ctrlState, LEFT_CTRL_PRESSED, WI_IsFlagSet(zeroKey, 0x200));
WI_SetFlagIf(ctrlState, LEFT_ALT_PRESSED, WI_IsFlagSet(zeroKey, 0x400));
_storage.push_back(SynthesizeKeyEvent(true, 1, LOBYTE(zeroKey), 0, wch, ctrlState));
continue;
}
_storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
}
}
TerminalInput& InputBuffer::GetTerminalInput()
{
return _termInput;

View File

@ -3,15 +3,11 @@
#pragma once
#include "readData.hpp"
#include "../types/inc/IInputEvent.hpp"
#include "../server/ObjectHandle.h"
#include "../server/ObjectHeader.h"
#include "../terminal/input/terminalInput.hpp"
#include <deque>
namespace Microsoft::Console::Render
{
class Renderer;
@ -21,7 +17,6 @@ namespace Microsoft::Console::Render
class InputBuffer final : public ConsoleObjectHeader
{
public:
DWORD InputMode;
ConsoleWaitQueue WaitQueue; // formerly ReadWaitQueue
bool fInComposition; // specifies if there's an ongoing text composition
@ -41,29 +36,36 @@ public:
const INPUT_RECORD& FetchWritePartialByteSequence() noexcept;
void StoreWritePartialByteSequence(const INPUT_RECORD& event) noexcept;
void ReinitializeInputBuffer();
void WakeUpReadersWaitingForData();
void TerminateRead(_In_ WaitTerminationReason Flag);
size_t GetNumberOfReadyEvents() const noexcept;
void Flush();
void FlushAllButKeys();
[[nodiscard]] NTSTATUS Read(_Out_ InputEventQueue& OutEvents,
const size_t AmountToRead,
const bool Peek,
const bool WaitForData,
const bool Unicode,
const bool Stream);
struct ReadDescriptor
{
bool wide;
bool records;
bool peek;
};
size_t Read(ReadDescriptor desc, void* data, size_t capacityInBytes);
size_t Prepend(const std::span<const INPUT_RECORD>& inEvents);
size_t Write(const INPUT_RECORD& inEvent);
size_t Write(const std::span<const INPUT_RECORD>& inEvents);
void WriteString(const std::wstring_view& text);
void Write(const INPUT_RECORD& record);
void Write(const std::span<const INPUT_RECORD>& records);
void Write(const std::wstring_view& text);
void WriteFocusEvent(bool focused) noexcept;
bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta);
bool IsInVirtualTerminalInputMode() const;
Microsoft::Console::VirtualTerminal::TerminalInput& GetTerminalInput();
// 1 INPUT_RECORD = 20 bytes = 10 wchar_t
// On 64-Bit architectures this results in std::list nodes of 1008 bytes (heap alloc headers are 16 bytes).
// Optimally this should use a single ring buffer and not a bunch of glued together container classes.
using RecordVec = til::small_vector<INPUT_RECORD, 48>;
using TextVec = til::small_vector<wchar_t, 480>;
using VecVariant = std::variant<RecordVec, TextVec>;
std::list<VecVariant> _storage;
private:
enum class ReadingMode : uint8_t
@ -81,23 +83,14 @@ private:
std::deque<INPUT_RECORD> _cachedInputEvents;
ReadingMode _readingMode = ReadingMode::StringA;
std::deque<INPUT_RECORD> _storage;
INPUT_RECORD _writePartialByteSequence{};
bool _writePartialByteSequenceAvailable = false;
Microsoft::Console::VirtualTerminal::TerminalInput _termInput;
// This flag is used in _HandleTerminalInputCallback
// If the InputBuffer leads to a _HandleTerminalInputCallback call,
// we should suppress the wakeup functions.
// Otherwise, we should be calling them.
bool _vtInputShouldSuppress{ false };
void _switchReadingMode(ReadingMode mode);
void _switchReadingModeSlowPath(ReadingMode mode);
void _WriteBuffer(const std::span<const INPUT_RECORD>& inRecords, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent);
bool _CoalesceEvent(const INPUT_RECORD& inEvent) noexcept;
void _HandleTerminalInputCallback(const Microsoft::Console::VirtualTerminal::TerminalInput::StringType& text);
void _writeString(const std::wstring_view& text);
#ifdef UNIT_TESTING
friend class InputBufferTests;

View File

@ -23,6 +23,8 @@ Revision History:
class INPUT_READ_HANDLE_DATA
{
public:
DWORD InputMode = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT;
INPUT_READ_HANDLE_DATA();
~INPUT_READ_HANDLE_DATA() = default;

View File

@ -33,11 +33,7 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) :
// - <none>
void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response)
{
// TODO GH#4954 During the input refactor we may want to add a "priority" input list
// to make sure that "response" input is spooled directly into the application.
// We switched this to an append (vs. a prepend) to fix GH#1637, a bug where two CPR
// could collide with each other.
_io.GetActiveInputBuffer()->WriteString(response);
_io.GetActiveInputBuffer()->Write(response);
}
// Routine Description:

View File

@ -392,22 +392,26 @@ size_t COOKED_READ_DATA::_wordNext(const std::wstring_view& chars, size_t positi
// Reads text off of the InputBuffer and dispatches it to the current popup or otherwise into the _buffer contents.
void COOKED_READ_DATA::_readCharInputLoop()
{
wchar_t buffer[128];
InputBuffer::ReadDescriptor readDesc{
.wide = true,
};
while (_state == State::Accumulating)
{
const auto hasPopup = !_popups.empty();
auto charOrVkey = UNICODE_NULL;
auto commandLineEditingKeys = false;
auto popupKeys = false;
const auto pCommandLineEditingKeys = hasPopup ? nullptr : &commandLineEditingKeys;
const auto pPopupKeys = hasPopup ? &popupKeys : nullptr;
//const auto pCommandLineEditingKeys = hasPopup ? nullptr : &commandLineEditingKeys;
//const auto pPopupKeys = hasPopup ? &popupKeys : nullptr;
DWORD modifiers = 0;
const auto status = GetChar(_pInputBuffer, &charOrVkey, true, pCommandLineEditingKeys, pPopupKeys, &modifiers);
if (status == CONSOLE_STATUS_WAIT)
const auto bytes = _pInputBuffer->Read(readDesc, &buffer, sizeof(buffer));
if (bytes == 0)
{
break;
}
THROW_IF_NTSTATUS_FAILED(status);
if (hasPopup)
{
@ -423,7 +427,9 @@ void COOKED_READ_DATA::_readCharInputLoop()
}
else
{
_handleChar(charOrVkey, modifiers);
const auto c = bytes / 2;
for (size_t i = 0; i < c; ++i)
_handleChar(buffer[i], modifiers);
}
}
}

View File

@ -93,34 +93,17 @@ try
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.
size_t amountToRead;
if (FAILED(SizeTSub(_eventReadCount, _outEvents.size(), &amountToRead)))
{
*pReplyStatus = STATUS_INTEGER_OVERFLOW;
return true;
}
*pReplyStatus = _pInputBuffer->Read(_outEvents,
amountToRead,
false,
false,
fIsUnicode,
false);
if (*pReplyStatus == CONSOLE_STATUS_WAIT)
const InputBuffer::ReadDescriptor readDesc{
.wide = fIsUnicode,
.records = true,
};
const auto count = _pInputBuffer->Read(readDesc, pOutputData, _eventReadCount * sizeof(INPUT_RECORD));
if (!count)
{
return false;
}
// move events to pOutputData
const auto pOutputDeque = static_cast<InputEventQueue* const>(pOutputData);
*pNumBytes = _outEvents.size() * sizeof(INPUT_RECORD);
*pOutputDeque = std::move(_outEvents);
*pNumBytes = count;
return true;
}
catch (...)

View File

@ -47,5 +47,4 @@ public:
private:
const size_t _eventReadCount;
InputEventQueue _outEvents;
};

View File

@ -105,148 +105,6 @@ static bool IsCommandLineEditingKey(const KEY_EVENT_RECORD& event)
return false;
}
// Routine Description:
// - This routine is used in stream input. It gets input and filters it for unicode characters.
// Arguments:
// - pInputBuffer - The InputBuffer to read from
// - pwchOut - On a successful read, the char data read
// - Wait - true if a waited read should be performed
// - pCommandLineEditingKeys - if present, arrow keys will be
// returned. on output, if true, pwchOut contains virtual key code for
// arrow key.
// - pPopupKeys - if present, arrow keys will be
// returned. on output, if true, pwchOut contains virtual key code for
// arrow key.
// Return Value:
// - STATUS_SUCCESS on success or a relevant error code on failure.
[[nodiscard]] NTSTATUS GetChar(_Inout_ InputBuffer* const pInputBuffer,
_Out_ wchar_t* const pwchOut,
const bool Wait,
_Out_opt_ bool* const pCommandLineEditingKeys,
_Out_opt_ bool* const pPopupKeys,
_Out_opt_ DWORD* const pdwKeyState) noexcept
{
if (nullptr != pCommandLineEditingKeys)
{
*pCommandLineEditingKeys = false;
}
if (nullptr != pPopupKeys)
{
*pPopupKeys = false;
}
if (nullptr != pdwKeyState)
{
*pdwKeyState = 0;
}
for (;;)
{
InputEventQueue events;
const auto Status = pInputBuffer->Read(events, 1, false, Wait, true, true);
if (FAILED_NTSTATUS(Status))
{
return Status;
}
if (events.empty())
{
assert(!Wait);
return STATUS_UNSUCCESSFUL;
}
const auto& Event = events[0];
if (Event.EventType == KEY_EVENT)
{
auto commandLineEditKey = false;
if (pCommandLineEditingKeys)
{
commandLineEditKey = IsCommandLineEditingKey(Event.Event.KeyEvent);
}
else if (pPopupKeys)
{
commandLineEditKey = IsCommandLinePopupKey(Event.Event.KeyEvent);
}
if (pdwKeyState)
{
*pdwKeyState = Event.Event.KeyEvent.dwControlKeyState;
}
if (Event.Event.KeyEvent.uChar.UnicodeChar != 0 && !commandLineEditKey)
{
// chars that are generated using alt + numpad
if (!Event.Event.KeyEvent.bKeyDown && Event.Event.KeyEvent.wVirtualKeyCode == VK_MENU)
{
if (WI_IsFlagSet(Event.Event.KeyEvent.dwControlKeyState, ALTNUMPAD_BIT))
{
if (HIBYTE(Event.Event.KeyEvent.uChar.UnicodeChar))
{
const char chT[2] = {
static_cast<char>(HIBYTE(Event.Event.KeyEvent.uChar.UnicodeChar)),
static_cast<char>(LOBYTE(Event.Event.KeyEvent.uChar.UnicodeChar)),
};
*pwchOut = CharToWchar(chT, 2);
}
else
{
// Because USER doesn't know our codepage,
// it gives us the raw OEM char and we
// convert it to a Unicode character.
char chT = LOBYTE(Event.Event.KeyEvent.uChar.UnicodeChar);
*pwchOut = CharToWchar(&chT, 1);
}
}
else
{
*pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
}
return STATUS_SUCCESS;
}
// Ignore Escape and Newline chars
if (Event.Event.KeyEvent.bKeyDown &&
(WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT) ||
(Event.Event.KeyEvent.wVirtualKeyCode != VK_ESCAPE &&
Event.Event.KeyEvent.uChar.UnicodeChar != UNICODE_LINEFEED)))
{
*pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
return STATUS_SUCCESS;
}
}
if (Event.Event.KeyEvent.bKeyDown)
{
if (pCommandLineEditingKeys && commandLineEditKey)
{
*pCommandLineEditingKeys = true;
*pwchOut = static_cast<wchar_t>(Event.Event.KeyEvent.wVirtualKeyCode);
return STATUS_SUCCESS;
}
if (pPopupKeys && commandLineEditKey)
{
*pPopupKeys = true;
*pwchOut = static_cast<char>(Event.Event.KeyEvent.wVirtualKeyCode);
return STATUS_SUCCESS;
}
const auto zeroKey = OneCoreSafeVkKeyScanW(0);
if (LOBYTE(zeroKey) == Event.Event.KeyEvent.wVirtualKeyCode &&
WI_IsAnyFlagSet(Event.Event.KeyEvent.dwControlKeyState, ALT_PRESSED) == WI_IsFlagSet(zeroKey, 0x400) &&
WI_IsAnyFlagSet(Event.Event.KeyEvent.dwControlKeyState, CTRL_PRESSED) == WI_IsFlagSet(zeroKey, 0x200) &&
WI_IsAnyFlagSet(Event.Event.KeyEvent.dwControlKeyState, SHIFT_PRESSED) == WI_IsFlagSet(zeroKey, 0x100))
{
// This really is the character 0x0000
*pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
return STATUS_SUCCESS;
}
}
}
}
}
// Routine Description:
// - if we have leftover input, copy as much fits into the user's
// buffer and return. we may have multi line input, if a macro
@ -404,43 +262,13 @@ NT_CATCH_RETURN()
try
{
UNREFERENCED_PARAMETER(readHandleState);
bytesRead = 0;
const auto charSize = unicode ? sizeof(wchar_t) : sizeof(char);
std::span writer{ buffer };
if (writer.size() < charSize)
{
return STATUS_BUFFER_TOO_SMALL;
}
inputBuffer.ConsumeCached(unicode, writer);
auto noDataReadYet = writer.size() == buffer.size();
auto status = STATUS_SUCCESS;
while (writer.size() >= charSize)
{
wchar_t wch;
// 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).
status = GetChar(&inputBuffer, &wch, noDataReadYet, nullptr, nullptr, nullptr);
if (FAILED_NTSTATUS(status))
{
break;
}
std::wstring_view wchView{ &wch, 1 };
inputBuffer.Consume(unicode, wchView, writer);
noDataReadYet = false;
}
bytesRead = buffer.size() - writer.size();
// Once we read some data off the InputBuffer it can't be read again, so we
// need to make sure to return a success status to the client in that case.
return noDataReadYet ? status : STATUS_SUCCESS;
const InputBuffer::ReadDescriptor readDesc{
.wide = unicode,
};
bytesRead = inputBuffer.Read(readDesc, buffer.data(), buffer.size());
return bytesRead == 0 ? CONSOLE_STATUS_WAIT : STATUS_SUCCESS;
}
NT_CATCH_RETURN()

View File

@ -17,15 +17,6 @@ Revision History:
#pragma once
#include "cmdline.h"
#include "../server/IWaitRoutine.h"
#include "readData.hpp"
[[nodiscard]] NTSTATUS GetChar(_Inout_ InputBuffer* const pInputBuffer,
_Out_ wchar_t* const pwchOut,
const bool Wait,
_Out_opt_ bool* const pCommandLineEditingKeys,
_Out_opt_ bool* const pPopupKeys,
_Out_opt_ DWORD* const pdwKeyState) noexcept;
[[nodiscard]] NTSTATUS ReadCharacterInput(InputBuffer& inputBuffer,
std::span<char> buffer,

View File

@ -444,81 +444,6 @@ class InputBufferTests
}
}
TEST_METHOD(CanPrependEvents)
{
InputBuffer inputBuffer;
// add some events so that we have something to stick in front of
INPUT_RECORD records[RECORD_INSERT_COUNT];
InputEventQueue inEvents;
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
{
records[i] = MakeKeyEvent(TRUE, 1, static_cast<WCHAR>(L'A' + i), 0, static_cast<WCHAR>(L'A' + i), 0);
inEvents.push_back(records[i]);
}
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
// prepend some other events
inEvents.clear();
INPUT_RECORD prependRecords[RECORD_INSERT_COUNT];
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
{
prependRecords[i] = MakeKeyEvent(TRUE, 1, static_cast<WCHAR>(L'a' + i), 0, static_cast<WCHAR>(L'a' + i), 0);
inEvents.push_back(prependRecords[i]);
}
auto eventsWritten = inputBuffer.Prepend(inEvents);
VERIFY_ARE_EQUAL(eventsWritten, RECORD_INSERT_COUNT);
// grab the first set of events and ensure they match prependRecords
InputEventQueue outEvents;
auto amountToRead = RECORD_INSERT_COUNT;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
amountToRead,
false,
false,
false,
false));
VERIFY_ARE_EQUAL(amountToRead, outEvents.size());
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_ARE_EQUAL(prependRecords[i], outEvents[i]);
}
outEvents.clear();
// verify the rest of the records
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
amountToRead,
false,
false,
false,
false));
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
VERIFY_ARE_EQUAL(amountToRead, outEvents.size());
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_ARE_EQUAL(records[i], outEvents[i]);
}
}
TEST_METHOD(CanReinitializeInputBuffer)
{
InputBuffer inputBuffer;
auto originalInputMode = inputBuffer.InputMode;
// change the buffer's state a bit
INPUT_RECORD record;
record.EventType = MENU_EVENT;
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 1u);
inputBuffer.InputMode = 0x0;
inputBuffer.ReinitializeInputBuffer();
// check that the changes were reverted
VERIFY_ARE_EQUAL(originalInputMode, inputBuffer.InputMode);
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
}
TEST_METHOD(HandleConsoleSuspensionEventsRemovesPauseKeys)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();

View File

@ -71,8 +71,9 @@ void Clipboard::Paste()
return;
}
auto pwstr = (PWCHAR)GlobalLock(ClipboardDataHandle);
StringPaste(pwstr, (ULONG)GlobalSize(ClipboardDataHandle) / sizeof(WCHAR));
const auto pwstr = (PWCHAR)GlobalLock(ClipboardDataHandle);
const auto len = wcsnlen(pwstr, GlobalSize(ClipboardDataHandle) / sizeof(WCHAR));
StringPaste({ pwstr, len });
// WIP auditing if user is enrolled
@ -94,22 +95,24 @@ Clipboard& Clipboard::Instance()
// - cchData - Size of the Unicode String in characters
// Return Value:
// - None
void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData,
const size_t cchData)
void Clipboard::StringPaste(const std::wstring_view& data)
{
if (pData == nullptr)
{
return;
}
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
try
{
const auto vtInputMode = gci.pInputBuffer->IsInVirtualTerminalInputMode();
const auto bracketedPasteMode = gci.GetBracketedPasteMode();
auto inEvents = TextToKeyEvents(pData, cchData, vtInputMode && bracketedPasteMode);
gci.pInputBuffer->Write(inEvents);
const auto bracketedPasteMode = vtInputMode && gci.GetBracketedPasteMode();
if (bracketedPasteMode)
{
gci.pInputBuffer->Write(L"\x1b[200~");
}
gci.pInputBuffer->Write(data);
if (bracketedPasteMode)
{
gci.pInputBuffer->Write(L"\x1b[201~");
}
}
catch (...)
{
@ -121,88 +124,6 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData,
#pragma region Private Methods
// Routine Description:
// - converts a wchar_t* into a series of KeyEvents as if it was typed
// from the keyboard
// Arguments:
// - pData - the text to convert
// - cchData - the size of pData, in wchars
// - bracketedPaste - should this be bracketed with paste control sequences
// Return Value:
// - deque of KeyEvents that represent the string passed in
// Note:
// - will throw exception on error
InputEventQueue Clipboard::TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
const size_t cchData,
const bool bracketedPaste)
{
THROW_HR_IF_NULL(E_INVALIDARG, pData);
InputEventQueue keyEvents;
const auto pushControlSequence = [&](const std::wstring_view sequence) {
std::for_each(sequence.begin(), sequence.end(), [&](const auto wch) {
keyEvents.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
keyEvents.push_back(SynthesizeKeyEvent(false, 1, 0, 0, wch, 0));
});
};
// When a bracketed paste is requested, we need to wrap the text with
// control sequences which indicate that the content has been pasted.
if (bracketedPaste)
{
pushControlSequence(L"\x1b[200~");
}
for (size_t i = 0; i < cchData; ++i)
{
auto currentChar = pData[i];
const auto charAllowed = FilterCharacterOnPaste(&currentChar);
// filter out linefeed if it's not the first char and preceded
// by a carriage return
const auto skipLinefeed = (i != 0 &&
currentChar == UNICODE_LINEFEED &&
pData[i - 1] == UNICODE_CARRIAGERETURN);
// filter out escape if bracketed paste mode is enabled
const auto skipEscape = (bracketedPaste && currentChar == UNICODE_ESC);
if (!charAllowed || skipLinefeed || skipEscape)
{
continue;
}
if (currentChar == 0)
{
break;
}
// MSFT:12123975 / WSL GH#2006
// If you paste text with ONLY linefeed line endings (unix style) in wsl,
// then we faithfully pass those along, which the underlying terminal
// interprets as C-j. In nano, C-j is mapped to "Justify text", which
// causes the pasted text to get broken at the width of the terminal.
// This behavior doesn't occur in gnome-terminal, and nothing like it occurs
// in vi or emacs.
// This change doesn't break pasting text into any of those applications
// with CR/LF (Windows) line endings either. That apparently always
// worked right.
if (IsInVirtualTerminalInputMode() && currentChar == UNICODE_LINEFEED)
{
currentChar = UNICODE_CARRIAGERETURN;
}
const auto codepage = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
CharToKeyEvents(currentChar, codepage, keyEvents);
}
if (bracketedPaste)
{
pushControlSequence(L"\x1b[201~");
}
return keyEvents;
}
// Routine Description:
// - Copies the selected area onto the global system clipboard.
// - NOTE: Throws on allocation and other clipboard failures.

View File

@ -30,15 +30,10 @@ namespace Microsoft::Console::Interactivity::Win32
static Clipboard& Instance();
void Copy(_In_ const bool fAlsoCopyFormatting = false);
void StringPaste(_In_reads_(cchData) PCWCHAR pwchData,
const size_t cchData);
void StringPaste(const std::wstring_view& data);
void Paste();
private:
InputEventQueue TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
const size_t cchData,
const bool bracketedPaste = false);
void StoreSelectionToClipboard(_In_ const bool fAlsoCopyFormatting);
void CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, _In_ const bool copyFormatting);

View File

@ -865,14 +865,14 @@ void Window::_HandleDrop(const WPARAM wParam) const
fAddQuotes = (wcschr(szPath, L' ') != nullptr);
if (fAddQuotes)
{
Clipboard::Instance().StringPaste(L"\"", 1);
Clipboard::Instance().StringPaste(L"\"");
}
Clipboard::Instance().StringPaste(szPath, wcslen(szPath));
Clipboard::Instance().StringPaste(szPath);
if (fAddQuotes)
{
Clipboard::Instance().StringPaste(L"\"", 1);
Clipboard::Instance().StringPaste(L"\"");
}
}
}

View File

@ -144,7 +144,7 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m)
RETURN_IF_FAILED(m->GetOutputBuffer(&pvBuffer, &cbBufferSize));
const auto rgRecords = reinterpret_cast<INPUT_RECORD*>(pvBuffer);
const auto cRecords = cbBufferSize / sizeof(INPUT_RECORD);
auto cRecords = cbBufferSize / sizeof(INPUT_RECORD);
const auto fIsPeek = WI_IsFlagSet(a->Flags, CONSOLE_READ_NOREMOVE);
const auto fIsWaitAllowed = WI_IsFlagClear(a->Flags, CONSOLE_READ_NOWAIT);
@ -152,11 +152,10 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m)
const auto pInputReadHandleData = pHandleData->GetClientInput();
std::unique_ptr<IWaitRoutine> waiter;
InputEventQueue outEvents;
auto hr = m->_pApiRoutines->GetConsoleInputImpl(
*pInputBuffer,
outEvents,
cRecords,
rgRecords,
&cRecords,
*pInputReadHandleData,
a->Unicode,
fIsPeek,
@ -164,10 +163,10 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m)
// We must return the number of records in the message payload (to alert the client)
// as well as in the message headers (below in SetReplyInformation) to alert the driver.
LOG_IF_FAILED(SizeTToULong(outEvents.size(), &a->NumRecords));
LOG_IF_FAILED(SizeTToULong(cRecords, &a->NumRecords));
size_t cbWritten;
LOG_IF_FAILED(SizeTMult(outEvents.size(), sizeof(INPUT_RECORD), &cbWritten));
LOG_IF_FAILED(SizeTMult(cRecords, sizeof(INPUT_RECORD), &cbWritten));
if (nullptr != waiter.get())
{
@ -195,10 +194,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m)
hr = S_OK;
}
}
else
{
std::ranges::copy(outEvents, rgRecords);
}
if (SUCCEEDED(hr))
{

View File

@ -67,8 +67,8 @@ public:
ULONG& events) noexcept = 0;
[[nodiscard]] virtual HRESULT GetConsoleInputImpl(IConsoleInputObject& context,
InputEventQueue& outEvents,
const size_t eventReadCount,
INPUT_RECORD* outEvents,
size_t* eventReadCount,
INPUT_READ_HANDLE_DATA& readHandleState,
const bool IsUnicode,
const bool IsPeek,

View File

@ -215,8 +215,7 @@ INPUT_READ_HANDLE_DATA* ConsoleHandleData::GetClientInput() const
// Routine Description:
// - This routine closes an input handle. It decrements the input buffer's
// reference count. If it goes to zero, the buffer is reinitialized.
// Otherwise, the handle is removed from sharing.
// reference count.
// Arguments:
// - <none>
// Return Value:
@ -244,11 +243,6 @@ INPUT_READ_HANDLE_DATA* ConsoleHandleData::GetClientInput() const
// TODO: MSFT: 9115192 - THIS IS BAD. It should use a destructor.
LOG_IF_FAILED(pInputBuffer->FreeIoHandle(this));
if (!pInputBuffer->HasAnyOpenHandles())
{
pInputBuffer->ReinitializeInputBuffer();
}
return S_OK;
}

View File

@ -65,18 +65,8 @@ bool InteractDispatch::WriteCtrlKey(const INPUT_RECORD& event)
// - True.
bool InteractDispatch::WriteString(const std::wstring_view string)
{
if (!string.empty())
{
const auto codepage = _api.GetConsoleOutputCP();
InputEventQueue keyEvents;
for (const auto& wch : string)
{
CharToKeyEvents(wch, codepage, keyEvents);
}
WriteInput(keyEvents);
}
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetActiveInputBuffer()->Write(string);
return true;
}

View File

@ -245,14 +245,7 @@ bool InputStateMachineEngine::ActionExecuteFromEscape(const wchar_t wch)
// - true iff we successfully dispatched the sequence.
bool InputStateMachineEngine::ActionPrint(const wchar_t wch)
{
short vkey = 0;
DWORD modifierState = 0;
auto success = _GenerateKeyFromChar(wch, vkey, modifierState);
if (success)
{
success = _WriteSingleKey(wch, vkey, modifierState);
}
return success;
return _pDispatch->WriteString({ &wch, 1 });
}
// Method Description:
@ -282,17 +275,7 @@ bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view st
{
if (_pDispatch->IsVtInputEnabled())
{
// Synthesize string into key events that we'll write to the buffer
// similar to TerminalInput::_SendInputSequence
if (!string.empty())
{
InputEventQueue inputEvents;
for (const auto& wch : string)
{
inputEvents.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
}
return _pDispatch->WriteInput(inputEvents);
}
return _pDispatch->WriteInput(string);
}
return ActionPrintString(string);
}
@ -330,8 +313,7 @@ bool InputStateMachineEngine::ActionEscDispatch(const VTID id)
if (success)
{
// Alt is definitely pressed in the esc+key case.
modifierState = WI_SetFlag(modifierState, LEFT_ALT_PRESSED);
WI_SetFlag(modifierState, LEFT_ALT_PRESSED);
success = _WriteSingleKey(wch, vk, modifierState);
}
}