terminal/src/host/inputBuffer.cpp

879 lines
31 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inputBuffer.hpp"
#include "dbcs.h"
#include "stream.h"
#include "../types/inc/GlyphWidth.hpp"
#include <functional>
#include "../interactivity/inc/ServiceLocator.hpp"
#define INPUT_BUFFER_DEFAULT_INPUT_MODE (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT)
using Microsoft::Console::Interactivity::ServiceLocator;
using Microsoft::Console::VirtualTerminal::TerminalInput;
// Routine Description:
// - This method creates an input buffer.
// Arguments:
// - None
// Return Value:
// - A new instance of InputBuffer
InputBuffer::InputBuffer() :
InputMode{ INPUT_BUFFER_DEFAULT_INPUT_MODE },
WaitQueue{},
_termInput(std::bind(&InputBuffer::_HandleTerminalInputCallback, this, std::placeholders::_1))
{
// The _termInput's constructor takes a reference to this object's _HandleTerminalInputCallback.
// We need to use std::bind to create a reference to that function without a reference to this InputBuffer
// initialize buffer header
fInComposition = false;
}
// Routine Description:
// - This routine frees the resources associated with an input buffer.
// Arguments:
// - None
// Return Value:
InputBuffer::~InputBuffer()
{
}
// 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()
{
return _readPartialByteSequence.get() != nullptr;
}
// 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<IInputEvent> InputBuffer::FetchReadPartialByteSequence(_In_ bool peek)
{
if (!IsReadPartialByteSequenceAvailable())
{
return std::unique_ptr<IInputEvent>();
}
if (peek)
{
return IInputEvent::Create(_readPartialByteSequence->ToInputRecord());
}
else
{
std::unique_ptr<IInputEvent> outEvent;
outEvent.swap(_readPartialByteSequence);
return outEvent;
}
}
// 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<IInputEvent> event)
{
_readPartialByteSequence.swap(event);
}
// Routine Description:
// - checks if any partial char data is available for writing
// operation.
// Arguments:
// - None
// Return Value:
// - true if partial char data is available, false otherwise
bool InputBuffer::IsWritePartialByteSequenceAvailable()
{
return _writePartialByteSequence.get() != nullptr;
}
// Routine Description:
// - writes any write 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<IInputEvent> InputBuffer::FetchWritePartialByteSequence(_In_ bool peek)
{
if (!IsWritePartialByteSequenceAvailable())
{
return std::unique_ptr<IInputEvent>();
}
if (peek)
{
return IInputEvent::Create(_writePartialByteSequence->ToInputRecord());
}
else
{
std::unique_ptr<IInputEvent> outEvent;
outEvent.swap(_writePartialByteSequence);
return outEvent;
}
}
// Routine Description:
// - stores partial write char data. will overwrite any previously
// stored data.
// Arguments:
// - event - The event to store
// Return Value:
// - None
void InputBuffer::StoreWritePartialByteSequence(std::unique_ptr<IInputEvent> event)
{
_writePartialByteSequence.swap(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:
// - None
// Return Value:
// - None
void InputBuffer::WakeUpReadersWaitingForData()
{
WaitQueue.NotifyWaiters(false);
}
// Routine Description:
// - Wakes up any readers waiting for data when a ctrl-c or ctrl-break is input.
// Arguments:
// - Flag - Indicates reason to terminate the readers.
// Return Value:
// - None
void InputBuffer::TerminateRead(_In_ WaitTerminationReason Flag)
{
WaitQueue.NotifyWaiters(true, Flag);
}
// Routine Description:
// - Returns the number of events in the input buffer.
// Arguments:
// - None
// Return Value:
// - The number of events currently in the input buffer.
// Note:
// - The console lock must be held when calling this routine.
size_t InputBuffer::GetNumberOfReadyEvents() const noexcept
{
return _storage.size();
}
// Routine Description:
// - This routine empties the input buffer
// Arguments:
// - None
// Return Value:
// - None
// Note:
// - The console lock must be held when calling this routine.
void InputBuffer::Flush()
{
_storage.clear();
ServiceLocator::LocateGlobals().hInputEvent.ResetEvent();
}
// Routine Description:
// - This routine removes all but the key events from the buffer.
// Arguments:
// - None
// Return Value:
// - None
// Note:
// - The console lock must be held when calling this routine.
void InputBuffer::FlushAllButKeys()
{
auto newEnd = std::remove_if(_storage.begin(), _storage.end(), [](const std::unique_ptr<IInputEvent>& event) {
return event->EventType() != InputEventType::KeyEvent;
});
_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_ std::deque<std::unique_ptr<IInputEvent>>& OutEvents,
const size_t AmountToRead,
const bool Peek,
const bool WaitForData,
const bool Unicode,
const bool Stream)
{
try
{
if (_storage.empty())
{
if (!WaitForData)
{
return STATUS_SUCCESS;
}
return CONSOLE_STATUS_WAIT;
}
// read from buffer
std::deque<std::unique_ptr<IInputEvent>> 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 (resetWaitEvent)
{
ServiceLocator::LocateGlobals().hInputEvent.ResetEvent();
}
return STATUS_SUCCESS;
}
catch (...)
{
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
}
// Routine Description:
// - This routine reads a single event 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:
// - outEvent - where the read event is stored
// - 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.
// 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_ std::unique_ptr<IInputEvent>& outEvent,
const bool Peek,
const bool WaitForData,
const bool Unicode,
const bool Stream)
{
NTSTATUS Status;
try
{
std::deque<std::unique_ptr<IInputEvent>> outEvents;
Status = Read(outEvents,
1,
Peek,
WaitForData,
Unicode,
Stream);
if (!outEvents.empty())
{
outEvent.swap(outEvents.front());
}
}
catch (...)
{
Status = NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
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:
// - <none>
// Note:
// - The console lock must be held when calling this routine.
void InputBuffer::_ReadBuffer(_Out_ std::deque<std::unique_ptr<IInputEvent>>& 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<std::unique_ptr<IInputEvent>> 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)
{
bool performNormalRead = true;
// for stream reads we need to split any key events that have been coalesced
if (streamRead)
{
if (_storage.front()->EventType() == InputEventType::KeyEvent)
{
KeyEvent* const pKeyEvent = static_cast<KeyEvent* const>(_storage.front().get());
if (pKeyEvent->GetRepeatCount() > 1)
{
// split the key event
std::unique_ptr<KeyEvent> streamKeyEvent = std::make_unique<KeyEvent>(*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 KeyEvent* const pKeyEvent = static_cast<const KeyEvent* const>(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<const KeyEvent&>(*readEvents.back()),
static_cast<const KeyEvent&>(*_storage.front())))
{
KeyEvent& keyEvent = static_cast<KeyEvent&>(*_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:
// - 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(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
{
try
{
_vtInputShouldSuppress = true;
auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; });
_HandleConsoleSuspensionEvents(inEvents);
if (inEvents.empty())
{
return STATUS_SUCCESS;
}
// 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<std::unique_ptr<IInputEvent>> 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.
bool unusedWaitStatus = false;
// write the prepend records
size_t prependEventsWritten;
_WriteBuffer(inEvents, prependEventsWritten, unusedWaitStatus);
FAIL_FAST_IF(!(unusedWaitStatus));
// write all previously existing records
size_t existingEventsWritten;
_WriteBuffer(existingStorage, existingEventsWritten, unusedWaitStatus);
FAIL_FAST_IF(!(!unusedWaitStatus));
// 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;
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return 0;
}
}
// 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(_Inout_ std::unique_ptr<IInputEvent> inEvent)
{
try
{
std::deque<std::unique_ptr<IInputEvent>> inEvents;
inEvents.push_back(std::move(inEvent));
return Write(inEvents);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return 0;
}
}
// 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(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
{
try
{
_vtInputShouldSuppress = true;
auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; });
_HandleConsoleSuspensionEvents(inEvents);
if (inEvents.empty())
{
return 0;
}
// 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;
}
}
// 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(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents,
_Out_ size_t& eventsWritten,
_Out_ bool& setWaitEvent)
{
eventsWritten = 0;
setWaitEvent = false;
const bool initiallyEmptyQueue = _storage.empty();
const size_t initialInEventsSize = inEvents.size();
const bool vtInputMode = IsInVirtualTerminalInputMode();
while (!inEvents.empty())
{
// Pop the next event.
// 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.
std::unique_ptr<IInputEvent> inEvent = std::move(inEvents.front());
inEvents.pop_front();
if (vtInputMode)
{
const bool handled = _termInput.HandleKey(inEvent.get());
if (handled)
{
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())
{
// coalescing requires a deque of events, so push it back onto the front.
inEvents.push_front(std::move(inEvent));
bool coalesced = false;
// this looks kinda weird but we don't want to coalesce a
// mouse event and then try to coalesce a key event right after.
//
// we also pass the whole deque to the coalescing methods
// even though they only want one event because it should
// be their responsibility to maintain the correct state
// of the deque if they process any records in it.
if (_CoalesceMouseMovedEvents(inEvents))
{
coalesced = true;
}
else if (_CoalesceRepeatedKeyPressEvents(inEvents))
{
coalesced = true;
}
if (coalesced)
{
eventsWritten = 1;
return;
}
else
{
// We didn't coalesce the event. pull it from the queue again,
// to keep the state consistent with the start of this block.
inEvent = std::move(inEvents.front());
inEvents.pop_front();
}
}
// At this point, the event was neither coalesced, nor processed by VT.
_storage.push_back(std::move(inEvent));
++eventsWritten;
}
if (initiallyEmptyQueue && !_storage.empty())
{
setWaitEvent = true;
}
}
// Routine Description:
// - Checks if the last saved event and the first event of inRecords are
// both MOUSE_MOVED events. If they are, the last saved event is
// updated with the new mouse position and the first event of inRecords is
// dropped.
// 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::_CoalesceMouseMovedEvents(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
{
FAIL_FAST_IF(!(inEvents.size() == 1));
FAIL_FAST_IF(_storage.empty());
const IInputEvent* const pFirstInEvent = inEvents.front().get();
const IInputEvent* const pLastStoredEvent = _storage.back().get();
if (pFirstInEvent->EventType() == InputEventType::MouseEvent &&
pLastStoredEvent->EventType() == InputEventType::MouseEvent)
{
const MouseEvent* const pInMouseEvent = static_cast<const MouseEvent* const>(pFirstInEvent);
const MouseEvent* const pLastMouseEvent = static_cast<const MouseEvent* const>(pLastStoredEvent);
if (pInMouseEvent->IsMouseMoveEvent() &&
pLastMouseEvent->IsMouseMoveEvent())
{
// update mouse moved position
MouseEvent* const pMouseEvent = static_cast<MouseEvent* const>(_storage.back().release());
pMouseEvent->SetPosition(pInMouseEvent->GetPosition());
std::unique_ptr<IInputEvent> tempPtr(pMouseEvent);
tempPtr.swap(_storage.back());
inEvents.pop_front();
return true;
}
}
return false;
}
// Routine Description:
// - checks two KeyEvents to see if they're similar enough to be coalesced
// Arguments:
// - a - the first KeyEvent
// - b - the other KeyEvent
// Return Value:
// - true if the events could be coalesced, false otherwise
bool InputBuffer::_CanCoalesce(const KeyEvent& a, const KeyEvent& b) const noexcept
{
if (WI_IsFlagSet(a.GetActiveModifierKeys(), NLS_IME_CONVERSION) &&
a.GetCharData() == b.GetCharData() &&
a.GetActiveModifierKeys() == b.GetActiveModifierKeys())
{
return true;
}
// other key events check
else if (a.GetVirtualScanCode() == b.GetVirtualScanCode() &&
a.GetCharData() == b.GetCharData() &&
a.GetActiveModifierKeys() == b.GetActiveModifierKeys())
{
return true;
}
return false;
}
// 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::_CoalesceRepeatedKeyPressEvents(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
{
FAIL_FAST_IF(!(inEvents.size() == 1));
FAIL_FAST_IF(_storage.empty());
const IInputEvent* const pFirstInEvent = inEvents.front().get();
const IInputEvent* const pLastStoredEvent = _storage.back().get();
if (pFirstInEvent->EventType() == InputEventType::KeyEvent &&
pLastStoredEvent->EventType() == InputEventType::KeyEvent)
{
const KeyEvent* const pInKeyEvent = static_cast<const KeyEvent* const>(pFirstInEvent);
const KeyEvent* const pLastKeyEvent = static_cast<const KeyEvent* const>(pLastStoredEvent);
if (pInKeyEvent->IsKeyDown() &&
pLastKeyEvent->IsKeyDown() &&
!IsGlyphFullWidth(pInKeyEvent->GetCharData()) &&
_CanCoalesce(*pInKeyEvent, *pLastKeyEvent))
{
// increment repeat count
KeyEvent* const pKeyEvent = static_cast<KeyEvent* const>(_storage.back().release());
WORD repeatCount = pKeyEvent->GetRepeatCount() + pInKeyEvent->GetRepeatCount();
pKeyEvent->SetRepeatCount(repeatCount);
std::unique_ptr<IInputEvent> tempPtr(pKeyEvent);
tempPtr.swap(_storage.back());
inEvents.pop_front();
return true;
}
}
return false;
}
// Routine Description:
// - Handles records that suspend/resume the console.
// Arguments:
// - records - records to check for pause/unpause events
// Return Value:
// - None
// Note:
// - The console lock must be held when calling this routine.
// - will throw exception on error
void InputBuffer::_HandleConsoleSuspensionEvents(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
std::deque<std::unique_ptr<IInputEvent>> outEvents;
while (!inEvents.empty())
{
std::unique_ptr<IInputEvent> currEvent = std::move(inEvents.front());
inEvents.pop_front();
if (currEvent->EventType() == InputEventType::KeyEvent)
{
const KeyEvent* const pKeyEvent = static_cast<const KeyEvent* const>(currEvent.get());
if (pKeyEvent->IsKeyDown())
{
if (WI_IsFlagSet(gci.Flags, CONSOLE_SUSPENDED) &&
!IsSystemKey(pKeyEvent->GetVirtualKeyCode()))
{
UnblockWriteConsole(CONSOLE_OUTPUT_SUSPENDED);
continue;
}
else if (WI_IsFlagSet(InputMode, ENABLE_LINE_INPUT) && pKeyEvent->IsPauseKey())
{
WI_SetFlag(gci.Flags, CONSOLE_SUSPENDED);
continue;
}
}
}
outEvents.push_back(std::move(currEvent));
}
inEvents.swap(outEvents);
}
// Routine Description:
// - Returns true if this input buffer is in VT Input mode.
// Arguments:
// <none>
// Return Value:
// - Returns true if this input buffer is in VT Input mode.
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(std::deque<std::unique_ptr<IInputEvent>>& inEvents)
{
try
{
// add all input events to the storage queue
while (!inEvents.empty())
{
std::unique_ptr<IInputEvent> inEvent = std::move(inEvents.front());
inEvents.pop_front();
_storage.push_back(std::move(inEvent));
}
if (!_vtInputShouldSuppress)
{
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
WakeUpReadersWaitingForData();
}
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
}
}
TerminalInput& InputBuffer::GetTerminalInput()
{
return _termInput;
}