wip
This commit is contained in:
parent
a4445ed4cc
commit
6126532e71
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (...)
|
||||
|
|
|
@ -47,5 +47,4 @@ public:
|
|||
|
||||
private:
|
||||
const size_t _eventReadCount;
|
||||
InputEventQueue _outEvents;
|
||||
};
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(¤tChar);
|
||||
// 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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue