mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-12 17:21:03 +00:00
Compare commits
1 Commits
dev/duhowe
...
dev/lhecke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6126532e71 |
@@ -116,7 +116,7 @@
|
|||||||
<WarningLevel>Level4</WarningLevel>
|
<WarningLevel>Level4</WarningLevel>
|
||||||
<TreatSpecificWarningsAsErrors>4189;4100;4242;4389;4244</TreatSpecificWarningsAsErrors>
|
<TreatSpecificWarningsAsErrors>4189;4100;4242;4389;4244</TreatSpecificWarningsAsErrors>
|
||||||
<!--<WarningLevel>EnableAllWarnings</WarningLevel>-->
|
<!--<WarningLevel>EnableAllWarnings</WarningLevel>-->
|
||||||
<TreatWarningAsError>true</TreatWarningAsError>
|
<!--<TreatWarningAsError>true</TreatWarningAsError>-->
|
||||||
<!--
|
<!--
|
||||||
C4201: nonstandard extension used: nameless struct/union
|
C4201: nonstandard extension used: nameless struct/union
|
||||||
Conhost code uses a lot of nameless structs/unions.
|
Conhost code uses a lot of nameless structs/unions.
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ public:
|
|||||||
ULONG& events) noexcept override;
|
ULONG& events) noexcept override;
|
||||||
|
|
||||||
[[nodiscard]] HRESULT GetConsoleInputImpl(IConsoleInputObject& context,
|
[[nodiscard]] HRESULT GetConsoleInputImpl(IConsoleInputObject& context,
|
||||||
InputEventQueue& outEvents,
|
INPUT_RECORD* outEvents,
|
||||||
const size_t eventReadCount,
|
size_t* eventReadCount,
|
||||||
INPUT_READ_HANDLE_DATA& readHandleState,
|
INPUT_READ_HANDLE_DATA& readHandleState,
|
||||||
const bool IsUnicode,
|
const bool IsUnicode,
|
||||||
const bool IsPeek,
|
const bool IsPeek,
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ void VtApiRoutines::_SynchronizeCursor(std::unique_ptr<IWaitRoutine>& waiter) no
|
|||||||
|
|
||||||
[[nodiscard]] HRESULT VtApiRoutines::GetConsoleInputImpl(
|
[[nodiscard]] HRESULT VtApiRoutines::GetConsoleInputImpl(
|
||||||
IConsoleInputObject& context,
|
IConsoleInputObject& context,
|
||||||
InputEventQueue& outEvents,
|
INPUT_RECORD* outEvents,
|
||||||
const size_t eventReadCount,
|
size_t* eventReadCount,
|
||||||
INPUT_READ_HANDLE_DATA& readHandleState,
|
INPUT_READ_HANDLE_DATA& readHandleState,
|
||||||
const bool IsUnicode,
|
const bool IsUnicode,
|
||||||
const bool IsPeek,
|
const bool IsPeek,
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ public:
|
|||||||
ULONG& events) noexcept override;
|
ULONG& events) noexcept override;
|
||||||
|
|
||||||
[[nodiscard]] HRESULT GetConsoleInputImpl(IConsoleInputObject& context,
|
[[nodiscard]] HRESULT GetConsoleInputImpl(IConsoleInputObject& context,
|
||||||
InputEventQueue& outEvents,
|
INPUT_RECORD* outEvents,
|
||||||
const size_t eventReadCount,
|
size_t* eventReadCount,
|
||||||
INPUT_READ_HANDLE_DATA& readHandleState,
|
INPUT_READ_HANDLE_DATA& readHandleState,
|
||||||
const bool IsUnicode,
|
const bool IsUnicode,
|
||||||
const bool IsPeek,
|
const bool IsPeek,
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ using Microsoft::Console::Interactivity::ServiceLocator;
|
|||||||
// block, this will be returned along with context in *ppWaiter.
|
// block, this will be returned along with context in *ppWaiter.
|
||||||
// - Or an out of memory/math/string error message in NTSTATUS format.
|
// - Or an out of memory/math/string error message in NTSTATUS format.
|
||||||
[[nodiscard]] HRESULT ApiRoutines::GetConsoleInputImpl(IConsoleInputObject& inputBuffer,
|
[[nodiscard]] HRESULT ApiRoutines::GetConsoleInputImpl(IConsoleInputObject& inputBuffer,
|
||||||
InputEventQueue& outEvents,
|
INPUT_RECORD* outEvents,
|
||||||
const size_t eventReadCount,
|
size_t* eventReadCount,
|
||||||
INPUT_READ_HANDLE_DATA& readHandleState,
|
INPUT_READ_HANDLE_DATA& readHandleState,
|
||||||
const bool IsUnicode,
|
const bool IsUnicode,
|
||||||
const bool IsPeek,
|
const bool IsPeek,
|
||||||
@@ -62,64 +62,30 @@ using Microsoft::Console::Interactivity::ServiceLocator;
|
|||||||
{
|
{
|
||||||
waiter.reset();
|
waiter.reset();
|
||||||
|
|
||||||
if (eventReadCount == 0)
|
if (*eventReadCount == 0)
|
||||||
{
|
{
|
||||||
return STATUS_SUCCESS;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
LockConsole();
|
LockConsole();
|
||||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||||
|
|
||||||
const auto Status = inputBuffer.Read(outEvents,
|
const InputBuffer::ReadDescriptor readDesc{
|
||||||
eventReadCount,
|
.wide = IsUnicode,
|
||||||
IsPeek,
|
.records = true,
|
||||||
true,
|
.peek = IsPeek,
|
||||||
IsUnicode,
|
};
|
||||||
false);
|
const auto count = inputBuffer.Read(readDesc, outEvents, *eventReadCount * sizeof(INPUT_RECORD));
|
||||||
|
if (count)
|
||||||
if (CONSOLE_STATUS_WAIT == Status)
|
|
||||||
{
|
{
|
||||||
// If we're told to wait until later, move all of our context
|
*eventReadCount = count / sizeof(INPUT_RECORD);
|
||||||
// to the read data object and send it back up to the server.
|
return S_OK;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
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();
|
CATCH_RETURN();
|
||||||
|
|
||||||
@@ -243,18 +213,20 @@ CATCH_RETURN();
|
|||||||
const std::span<const INPUT_RECORD> buffer,
|
const std::span<const INPUT_RECORD> buffer,
|
||||||
size_t& written,
|
size_t& written,
|
||||||
const bool append) noexcept
|
const bool append) noexcept
|
||||||
|
try
|
||||||
{
|
{
|
||||||
written = 0;
|
written = 0;
|
||||||
|
|
||||||
LockConsole();
|
LockConsole();
|
||||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||||
|
|
||||||
try
|
// TODO: append/prepend
|
||||||
{
|
UNREFERENCED_PARAMETER(append);
|
||||||
return _WriteConsoleInputWImplHelper(context, buffer, written, append);
|
context.Write(buffer);
|
||||||
}
|
written = buffer.size();
|
||||||
CATCH_RETURN();
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
CATCH_RETURN();
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - This is used when the app is reading output as cells and needs them converted
|
// - 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;
|
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)
|
BOOL IsSystemKey(const WORD wVirtualKeyCode)
|
||||||
{
|
{
|
||||||
switch (wVirtualKeyCode)
|
switch (wVirtualKeyCode)
|
||||||
@@ -197,23 +191,12 @@ void HandleFocusEvent(const BOOL fSetFocus)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HandleMenuEvent(const DWORD wParam)
|
void HandleMenuEvent(const DWORD wParam)
|
||||||
|
try
|
||||||
{
|
{
|
||||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||||
|
gci.pInputBuffer->Write(SynthesizeMenuEvent(wParam));
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
CATCH_LOG()
|
||||||
|
|
||||||
void HandleCtrlEvent(const DWORD EventType)
|
void HandleCtrlEvent(const DWORD EventType)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||||
#include "../types/inc/GlyphWidth.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::Interactivity::ServiceLocator;
|
||||||
using Microsoft::Console::VirtualTerminal::TerminalInput;
|
using Microsoft::Console::VirtualTerminal::TerminalInput;
|
||||||
@@ -24,8 +24,7 @@ using namespace Microsoft::Console;
|
|||||||
// - None
|
// - None
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - A new instance of InputBuffer
|
// - A new instance of InputBuffer
|
||||||
InputBuffer::InputBuffer() :
|
InputBuffer::InputBuffer()
|
||||||
InputMode{ INPUT_BUFFER_DEFAULT_INPUT_MODE }
|
|
||||||
{
|
{
|
||||||
// initialize buffer header
|
// initialize buffer header
|
||||||
fInComposition = false;
|
fInComposition = false;
|
||||||
@@ -168,18 +167,7 @@ void InputBuffer::Cache(std::wstring_view source)
|
|||||||
// Moves up to `count`, previously cached events into `target`.
|
// Moves up to `count`, previously cached events into `target`.
|
||||||
size_t InputBuffer::ConsumeCached(bool isUnicode, size_t count, InputEventQueue& target)
|
size_t InputBuffer::ConsumeCached(bool isUnicode, size_t count, InputEventQueue& target)
|
||||||
{
|
{
|
||||||
_switchReadingMode(isUnicode ? ReadingMode::InputEventsW : ReadingMode::InputEventsA);
|
return 0;
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
while (i < count && !_cachedInputEvents.empty())
|
|
||||||
{
|
|
||||||
target.push_back(std::move(_cachedInputEvents.front()));
|
|
||||||
_cachedInputEvents.pop_front();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copies up to `count`, previously cached events into `target`.
|
// 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)
|
if (source.size() > expectedSourceSize)
|
||||||
{
|
{
|
||||||
_cachedInputEvents.insert(
|
|
||||||
_cachedInputEvents.end(),
|
|
||||||
std::make_move_iterator(source.begin() + expectedSourceSize),
|
|
||||||
std::make_move_iterator(source.end()));
|
|
||||||
source.resize(expectedSourceSize);
|
source.resize(expectedSourceSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,8 +219,6 @@ void InputBuffer::_switchReadingModeSlowPath(ReadingMode mode)
|
|||||||
_cachedTextW = std::wstring{};
|
_cachedTextW = std::wstring{};
|
||||||
_cachedTextReaderW = {};
|
_cachedTextReaderW = {};
|
||||||
|
|
||||||
_cachedInputEvents = std::deque<INPUT_RECORD>{};
|
|
||||||
|
|
||||||
_readingMode = mode;
|
_readingMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,19 +259,6 @@ void InputBuffer::StoreWritePartialByteSequence(const INPUT_RECORD& event) noexc
|
|||||||
_writePartialByteSequence = event;
|
_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:
|
// Routine Description:
|
||||||
// - Wakes up readers waiting for data to read.
|
// - Wakes up readers waiting for data to read.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
@@ -322,7 +291,7 @@ void InputBuffer::TerminateRead(_In_ WaitTerminationReason Flag)
|
|||||||
// - The console lock must be held when calling this routine.
|
// - The console lock must be held when calling this routine.
|
||||||
size_t InputBuffer::GetNumberOfReadyEvents() const noexcept
|
size_t InputBuffer::GetNumberOfReadyEvents() const noexcept
|
||||||
{
|
{
|
||||||
return _storage.size();
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -349,260 +318,197 @@ void InputBuffer::Flush()
|
|||||||
// - The console lock must be held when calling this routine.
|
// - The console lock must be held when calling this routine.
|
||||||
void InputBuffer::FlushAllButKeys()
|
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:
|
static void transfer(InputBuffer::RecordVec& in, std::span<INPUT_RECORD> out)
|
||||||
// - 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
|
|
||||||
{
|
{
|
||||||
assert(OutEvents.empty());
|
const auto count = std::min(in.size(), out.size());
|
||||||
|
std::copy_n(in.begin(), count, out.begin());
|
||||||
const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().CP;
|
if (count == out.size())
|
||||||
|
|
||||||
if (Peek)
|
|
||||||
{
|
{
|
||||||
PeekCached(Unicode, AmountToRead, OutEvents);
|
in.clear();
|
||||||
}
|
}
|
||||||
else
|
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();
|
auto it = _storage.begin();
|
||||||
const auto end = _storage.end();
|
const auto end = _storage.end();
|
||||||
|
|
||||||
while (it != end && OutEvents.size() < AmountToRead)
|
for (; it != end; ++it)
|
||||||
{
|
{
|
||||||
if (it->EventType == KEY_EVENT)
|
std::visit(
|
||||||
{
|
[&]<typename T>(T& arg) {
|
||||||
auto event = *it;
|
if constexpr (std::is_same_v<T, RecordVec>)
|
||||||
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
|
|
||||||
{
|
{
|
||||||
OutEvents.push_back(event);
|
if (desc.records)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// char is signed and assigning it to UnicodeChar would cause sign-extension.
|
transfer(arg, { static_cast<INPUT_RECORD*>(data), capacityInBytes / sizeof(INPUT_RECORD) });
|
||||||
// unsigned char doesn't have this problem.
|
|
||||||
event.Event.KeyEvent.uChar.UnicodeChar = til::bit_cast<uint8_t>(ch);
|
|
||||||
OutEvents.push_back(event);
|
|
||||||
}
|
}
|
||||||
repeat--;
|
else
|
||||||
} while (repeat > 0 && OutEvents.size() < AmountToRead);
|
{
|
||||||
}
|
transfer(arg, { static_cast<wchar_t*>(data), capacityInBytes / sizeof(wchar_t) });
|
||||||
|
}
|
||||||
if (repeat && !Peek)
|
}
|
||||||
{
|
else if constexpr (std::is_same_v<T, TextVec>)
|
||||||
it->Event.KeyEvent.wRepeatCount = repeat;
|
{
|
||||||
break;
|
if (desc.records)
|
||||||
}
|
{
|
||||||
}
|
transfer(arg, { static_cast<INPUT_RECORD*>(data), capacityInBytes / sizeof(INPUT_RECORD) });
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
OutEvents.push_back(*it);
|
{
|
||||||
}
|
transfer(arg, { static_cast<wchar_t*>(data), capacityInBytes / sizeof(wchar_t) });
|
||||||
|
}
|
||||||
++it;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static_assert(sizeof(arg) == 0, "non-exhaustive visitor!");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*it);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Peek)
|
if (!desc.peek)
|
||||||
{
|
{
|
||||||
_storage.erase(_storage.begin(), it);
|
_storage.erase(_storage.begin(), it);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache(Unicode, OutEvents, AmountToRead);
|
return capacityInBytes - remaining;
|
||||||
|
|
||||||
if (OutEvents.empty())
|
|
||||||
{
|
|
||||||
return WaitForData ? CONSOLE_STATUS_WAIT : STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
if (_storage.empty())
|
|
||||||
{
|
|
||||||
ServiceLocator::LocateGlobals().hInputEvent.ResetEvent();
|
|
||||||
}
|
|
||||||
return STATUS_SUCCESS;
|
|
||||||
}
|
}
|
||||||
catch (...)
|
|
||||||
|
void InputBuffer::Write(const INPUT_RECORD& record)
|
||||||
{
|
{
|
||||||
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
|
Write(std::span{ &record, 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
void InputBuffer::Write(const std::span<const INPUT_RECORD>& records)
|
||||||
// - Writes events to the beginning of the input buffer.
|
try
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
try
|
if (records.empty())
|
||||||
{
|
{
|
||||||
if (inEvents.empty())
|
return;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
catch (...)
|
|
||||||
|
const auto initiallyEmpty = _storage.empty();
|
||||||
|
|
||||||
|
if (initiallyEmpty || _storage.back().index() != 0)
|
||||||
{
|
{
|
||||||
LOG_HR(wil::ResultFromCaughtException());
|
_storage.emplace_back(RecordVec{});
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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:
|
void InputBuffer::Write(const std::wstring_view& text)
|
||||||
// - 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)
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (text.empty())
|
if (text.empty())
|
||||||
@@ -610,15 +516,20 @@ try
|
|||||||
return;
|
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();
|
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
WakeUpReadersWaitingForData();
|
WakeUpReadersWaitingForData();
|
||||||
}
|
}
|
||||||
CATCH_LOG()
|
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.
|
// 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
|
void InputBuffer::WriteFocusEvent(bool focused) noexcept
|
||||||
{
|
{
|
||||||
if (IsInVirtualTerminalInputMode())
|
//Write(SynthesizeFocusEvent(focused));
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true when mouse input started. You should then capture the mouse and produce further events.
|
// 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)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,135 +561,6 @@ static bool IsPauseKey(const KEY_EVENT_RECORD& event)
|
|||||||
return ctrlButNotAlt && event.wVirtualKeyCode == L'S';
|
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:
|
// Routine Description:
|
||||||
// - Returns true if this input buffer is in VT Input mode.
|
// - Returns true if this input buffer is in VT Input mode.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
@@ -833,55 +572,6 @@ bool InputBuffer::IsInVirtualTerminalInputMode() const
|
|||||||
return WI_IsFlagSet(InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT);
|
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()
|
TerminalInput& InputBuffer::GetTerminalInput()
|
||||||
{
|
{
|
||||||
return _termInput;
|
return _termInput;
|
||||||
|
|||||||
@@ -3,15 +3,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "readData.hpp"
|
|
||||||
#include "../types/inc/IInputEvent.hpp"
|
#include "../types/inc/IInputEvent.hpp"
|
||||||
|
|
||||||
#include "../server/ObjectHandle.h"
|
#include "../server/ObjectHandle.h"
|
||||||
#include "../server/ObjectHeader.h"
|
#include "../server/ObjectHeader.h"
|
||||||
#include "../terminal/input/terminalInput.hpp"
|
#include "../terminal/input/terminalInput.hpp"
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
|
|
||||||
namespace Microsoft::Console::Render
|
namespace Microsoft::Console::Render
|
||||||
{
|
{
|
||||||
class Renderer;
|
class Renderer;
|
||||||
@@ -21,7 +17,6 @@ namespace Microsoft::Console::Render
|
|||||||
class InputBuffer final : public ConsoleObjectHeader
|
class InputBuffer final : public ConsoleObjectHeader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DWORD InputMode;
|
|
||||||
ConsoleWaitQueue WaitQueue; // formerly ReadWaitQueue
|
ConsoleWaitQueue WaitQueue; // formerly ReadWaitQueue
|
||||||
bool fInComposition; // specifies if there's an ongoing text composition
|
bool fInComposition; // specifies if there's an ongoing text composition
|
||||||
|
|
||||||
@@ -41,29 +36,36 @@ public:
|
|||||||
const INPUT_RECORD& FetchWritePartialByteSequence() noexcept;
|
const INPUT_RECORD& FetchWritePartialByteSequence() noexcept;
|
||||||
void StoreWritePartialByteSequence(const INPUT_RECORD& event) noexcept;
|
void StoreWritePartialByteSequence(const INPUT_RECORD& event) noexcept;
|
||||||
|
|
||||||
void ReinitializeInputBuffer();
|
|
||||||
void WakeUpReadersWaitingForData();
|
void WakeUpReadersWaitingForData();
|
||||||
void TerminateRead(_In_ WaitTerminationReason Flag);
|
void TerminateRead(_In_ WaitTerminationReason Flag);
|
||||||
size_t GetNumberOfReadyEvents() const noexcept;
|
size_t GetNumberOfReadyEvents() const noexcept;
|
||||||
void Flush();
|
void Flush();
|
||||||
void FlushAllButKeys();
|
void FlushAllButKeys();
|
||||||
|
|
||||||
[[nodiscard]] NTSTATUS Read(_Out_ InputEventQueue& OutEvents,
|
struct ReadDescriptor
|
||||||
const size_t AmountToRead,
|
{
|
||||||
const bool Peek,
|
bool wide;
|
||||||
const bool WaitForData,
|
bool records;
|
||||||
const bool Unicode,
|
bool peek;
|
||||||
const bool Stream);
|
};
|
||||||
|
size_t Read(ReadDescriptor desc, void* data, size_t capacityInBytes);
|
||||||
|
|
||||||
size_t Prepend(const std::span<const INPUT_RECORD>& inEvents);
|
void Write(const INPUT_RECORD& record);
|
||||||
size_t Write(const INPUT_RECORD& inEvent);
|
void Write(const std::span<const INPUT_RECORD>& records);
|
||||||
size_t Write(const std::span<const INPUT_RECORD>& inEvents);
|
void Write(const std::wstring_view& text);
|
||||||
void WriteString(const std::wstring_view& text);
|
|
||||||
void WriteFocusEvent(bool focused) noexcept;
|
void WriteFocusEvent(bool focused) noexcept;
|
||||||
bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta);
|
bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta);
|
||||||
|
|
||||||
bool IsInVirtualTerminalInputMode() const;
|
bool IsInVirtualTerminalInputMode() const;
|
||||||
Microsoft::Console::VirtualTerminal::TerminalInput& GetTerminalInput();
|
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:
|
private:
|
||||||
enum class ReadingMode : uint8_t
|
enum class ReadingMode : uint8_t
|
||||||
@@ -81,23 +83,14 @@ private:
|
|||||||
std::deque<INPUT_RECORD> _cachedInputEvents;
|
std::deque<INPUT_RECORD> _cachedInputEvents;
|
||||||
ReadingMode _readingMode = ReadingMode::StringA;
|
ReadingMode _readingMode = ReadingMode::StringA;
|
||||||
|
|
||||||
std::deque<INPUT_RECORD> _storage;
|
|
||||||
INPUT_RECORD _writePartialByteSequence{};
|
INPUT_RECORD _writePartialByteSequence{};
|
||||||
bool _writePartialByteSequenceAvailable = false;
|
bool _writePartialByteSequenceAvailable = false;
|
||||||
Microsoft::Console::VirtualTerminal::TerminalInput _termInput;
|
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 _switchReadingMode(ReadingMode mode);
|
||||||
void _switchReadingModeSlowPath(ReadingMode mode);
|
void _switchReadingModeSlowPath(ReadingMode mode);
|
||||||
void _WriteBuffer(const std::span<const INPUT_RECORD>& inRecords, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent);
|
void _WriteBuffer(const std::span<const INPUT_RECORD>& inRecords, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent);
|
||||||
bool _CoalesceEvent(const INPUT_RECORD& inEvent) noexcept;
|
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
|
#ifdef UNIT_TESTING
|
||||||
friend class InputBufferTests;
|
friend class InputBufferTests;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ Revision History:
|
|||||||
class INPUT_READ_HANDLE_DATA
|
class INPUT_READ_HANDLE_DATA
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
DWORD InputMode = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT;
|
||||||
|
|
||||||
INPUT_READ_HANDLE_DATA();
|
INPUT_READ_HANDLE_DATA();
|
||||||
|
|
||||||
~INPUT_READ_HANDLE_DATA() = default;
|
~INPUT_READ_HANDLE_DATA() = default;
|
||||||
|
|||||||
@@ -33,11 +33,7 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) :
|
|||||||
// - <none>
|
// - <none>
|
||||||
void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response)
|
void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response)
|
||||||
{
|
{
|
||||||
// TODO GH#4954 During the input refactor we may want to add a "priority" input list
|
_io.GetActiveInputBuffer()->Write(response);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// 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.
|
// Reads text off of the InputBuffer and dispatches it to the current popup or otherwise into the _buffer contents.
|
||||||
void COOKED_READ_DATA::_readCharInputLoop()
|
void COOKED_READ_DATA::_readCharInputLoop()
|
||||||
{
|
{
|
||||||
|
wchar_t buffer[128];
|
||||||
|
InputBuffer::ReadDescriptor readDesc{
|
||||||
|
.wide = true,
|
||||||
|
};
|
||||||
|
|
||||||
while (_state == State::Accumulating)
|
while (_state == State::Accumulating)
|
||||||
{
|
{
|
||||||
const auto hasPopup = !_popups.empty();
|
const auto hasPopup = !_popups.empty();
|
||||||
auto charOrVkey = UNICODE_NULL;
|
auto charOrVkey = UNICODE_NULL;
|
||||||
auto commandLineEditingKeys = false;
|
auto commandLineEditingKeys = false;
|
||||||
auto popupKeys = false;
|
auto popupKeys = false;
|
||||||
const auto pCommandLineEditingKeys = hasPopup ? nullptr : &commandLineEditingKeys;
|
//const auto pCommandLineEditingKeys = hasPopup ? nullptr : &commandLineEditingKeys;
|
||||||
const auto pPopupKeys = hasPopup ? &popupKeys : nullptr;
|
//const auto pPopupKeys = hasPopup ? &popupKeys : nullptr;
|
||||||
DWORD modifiers = 0;
|
DWORD modifiers = 0;
|
||||||
|
|
||||||
const auto status = GetChar(_pInputBuffer, &charOrVkey, true, pCommandLineEditingKeys, pPopupKeys, &modifiers);
|
const auto bytes = _pInputBuffer->Read(readDesc, &buffer, sizeof(buffer));
|
||||||
if (status == CONSOLE_STATUS_WAIT)
|
if (bytes == 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
THROW_IF_NTSTATUS_FAILED(status);
|
|
||||||
|
|
||||||
if (hasPopup)
|
if (hasPopup)
|
||||||
{
|
{
|
||||||
@@ -423,7 +427,9 @@ void COOKED_READ_DATA::_readCharInputLoop()
|
|||||||
}
|
}
|
||||||
else
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we get to here, this routine was called either by the input
|
const InputBuffer::ReadDescriptor readDesc{
|
||||||
// thread or a write routine. both of these callers grab the
|
.wide = fIsUnicode,
|
||||||
// current console lock.
|
.records = true,
|
||||||
|
};
|
||||||
size_t amountToRead;
|
const auto count = _pInputBuffer->Read(readDesc, pOutputData, _eventReadCount * sizeof(INPUT_RECORD));
|
||||||
if (FAILED(SizeTSub(_eventReadCount, _outEvents.size(), &amountToRead)))
|
if (!count)
|
||||||
{
|
|
||||||
*pReplyStatus = STATUS_INTEGER_OVERFLOW;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
*pReplyStatus = _pInputBuffer->Read(_outEvents,
|
|
||||||
amountToRead,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
fIsUnicode,
|
|
||||||
false);
|
|
||||||
|
|
||||||
if (*pReplyStatus == CONSOLE_STATUS_WAIT)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// move events to pOutputData
|
*pNumBytes = count;
|
||||||
const auto pOutputDeque = static_cast<InputEventQueue* const>(pOutputData);
|
|
||||||
*pNumBytes = _outEvents.size() * sizeof(INPUT_RECORD);
|
|
||||||
*pOutputDeque = std::move(_outEvents);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
|
|||||||
@@ -47,5 +47,4 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
const size_t _eventReadCount;
|
const size_t _eventReadCount;
|
||||||
InputEventQueue _outEvents;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -105,148 +105,6 @@ static bool IsCommandLineEditingKey(const KEY_EVENT_RECORD& event)
|
|||||||
return false;
|
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:
|
// Routine Description:
|
||||||
// - if we have leftover input, copy as much fits into the user's
|
// - 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
|
// buffer and return. we may have multi line input, if a macro
|
||||||
@@ -404,43 +262,13 @@ NT_CATCH_RETURN()
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
UNREFERENCED_PARAMETER(readHandleState);
|
UNREFERENCED_PARAMETER(readHandleState);
|
||||||
|
|
||||||
bytesRead = 0;
|
bytesRead = 0;
|
||||||
|
|
||||||
const auto charSize = unicode ? sizeof(wchar_t) : sizeof(char);
|
const InputBuffer::ReadDescriptor readDesc{
|
||||||
std::span writer{ buffer };
|
.wide = unicode,
|
||||||
|
};
|
||||||
if (writer.size() < charSize)
|
bytesRead = inputBuffer.Read(readDesc, buffer.data(), buffer.size());
|
||||||
{
|
return bytesRead == 0 ? CONSOLE_STATUS_WAIT : STATUS_SUCCESS;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
NT_CATCH_RETURN()
|
NT_CATCH_RETURN()
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,6 @@ Revision History:
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "cmdline.h"
|
#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,
|
[[nodiscard]] NTSTATUS ReadCharacterInput(InputBuffer& inputBuffer,
|
||||||
std::span<char> buffer,
|
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)
|
TEST_METHOD(HandleConsoleSuspensionEventsRemovesPauseKeys)
|
||||||
{
|
{
|
||||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||||
|
|||||||
@@ -71,8 +71,9 @@ void Clipboard::Paste()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pwstr = (PWCHAR)GlobalLock(ClipboardDataHandle);
|
const auto pwstr = (PWCHAR)GlobalLock(ClipboardDataHandle);
|
||||||
StringPaste(pwstr, (ULONG)GlobalSize(ClipboardDataHandle) / sizeof(WCHAR));
|
const auto len = wcsnlen(pwstr, GlobalSize(ClipboardDataHandle) / sizeof(WCHAR));
|
||||||
|
StringPaste({ pwstr, len });
|
||||||
|
|
||||||
// WIP auditing if user is enrolled
|
// WIP auditing if user is enrolled
|
||||||
|
|
||||||
@@ -94,22 +95,24 @@ Clipboard& Clipboard::Instance()
|
|||||||
// - cchData - Size of the Unicode String in characters
|
// - cchData - Size of the Unicode String in characters
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - None
|
// - None
|
||||||
void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData,
|
void Clipboard::StringPaste(const std::wstring_view& data)
|
||||||
const size_t cchData)
|
|
||||||
{
|
{
|
||||||
if (pData == nullptr)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const auto vtInputMode = gci.pInputBuffer->IsInVirtualTerminalInputMode();
|
const auto vtInputMode = gci.pInputBuffer->IsInVirtualTerminalInputMode();
|
||||||
const auto bracketedPasteMode = gci.GetBracketedPasteMode();
|
const auto bracketedPasteMode = vtInputMode && gci.GetBracketedPasteMode();
|
||||||
auto inEvents = TextToKeyEvents(pData, cchData, vtInputMode && bracketedPasteMode);
|
|
||||||
gci.pInputBuffer->Write(inEvents);
|
if (bracketedPasteMode)
|
||||||
|
{
|
||||||
|
gci.pInputBuffer->Write(L"\x1b[200~");
|
||||||
|
}
|
||||||
|
gci.pInputBuffer->Write(data);
|
||||||
|
if (bracketedPasteMode)
|
||||||
|
{
|
||||||
|
gci.pInputBuffer->Write(L"\x1b[201~");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
@@ -121,88 +124,6 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData,
|
|||||||
|
|
||||||
#pragma region Private Methods
|
#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:
|
// Routine Description:
|
||||||
// - Copies the selected area onto the global system clipboard.
|
// - Copies the selected area onto the global system clipboard.
|
||||||
// - NOTE: Throws on allocation and other clipboard failures.
|
// - NOTE: Throws on allocation and other clipboard failures.
|
||||||
|
|||||||
@@ -30,15 +30,10 @@ namespace Microsoft::Console::Interactivity::Win32
|
|||||||
static Clipboard& Instance();
|
static Clipboard& Instance();
|
||||||
|
|
||||||
void Copy(_In_ const bool fAlsoCopyFormatting = false);
|
void Copy(_In_ const bool fAlsoCopyFormatting = false);
|
||||||
void StringPaste(_In_reads_(cchData) PCWCHAR pwchData,
|
void StringPaste(const std::wstring_view& data);
|
||||||
const size_t cchData);
|
|
||||||
void Paste();
|
void Paste();
|
||||||
|
|
||||||
private:
|
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 StoreSelectionToClipboard(_In_ const bool fAlsoCopyFormatting);
|
||||||
|
|
||||||
void CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, _In_ const bool copyFormatting);
|
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);
|
fAddQuotes = (wcschr(szPath, L' ') != nullptr);
|
||||||
if (fAddQuotes)
|
if (fAddQuotes)
|
||||||
{
|
{
|
||||||
Clipboard::Instance().StringPaste(L"\"", 1);
|
Clipboard::Instance().StringPaste(L"\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
Clipboard::Instance().StringPaste(szPath, wcslen(szPath));
|
Clipboard::Instance().StringPaste(szPath);
|
||||||
|
|
||||||
if (fAddQuotes)
|
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));
|
RETURN_IF_FAILED(m->GetOutputBuffer(&pvBuffer, &cbBufferSize));
|
||||||
|
|
||||||
const auto rgRecords = reinterpret_cast<INPUT_RECORD*>(pvBuffer);
|
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 fIsPeek = WI_IsFlagSet(a->Flags, CONSOLE_READ_NOREMOVE);
|
||||||
const auto fIsWaitAllowed = WI_IsFlagClear(a->Flags, CONSOLE_READ_NOWAIT);
|
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();
|
const auto pInputReadHandleData = pHandleData->GetClientInput();
|
||||||
|
|
||||||
std::unique_ptr<IWaitRoutine> waiter;
|
std::unique_ptr<IWaitRoutine> waiter;
|
||||||
InputEventQueue outEvents;
|
|
||||||
auto hr = m->_pApiRoutines->GetConsoleInputImpl(
|
auto hr = m->_pApiRoutines->GetConsoleInputImpl(
|
||||||
*pInputBuffer,
|
*pInputBuffer,
|
||||||
outEvents,
|
rgRecords,
|
||||||
cRecords,
|
&cRecords,
|
||||||
*pInputReadHandleData,
|
*pInputReadHandleData,
|
||||||
a->Unicode,
|
a->Unicode,
|
||||||
fIsPeek,
|
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)
|
// 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.
|
// 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;
|
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())
|
if (nullptr != waiter.get())
|
||||||
{
|
{
|
||||||
@@ -195,10 +194,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m)
|
|||||||
hr = S_OK;
|
hr = S_OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
std::ranges::copy(outEvents, rgRecords);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SUCCEEDED(hr))
|
if (SUCCEEDED(hr))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ public:
|
|||||||
ULONG& events) noexcept = 0;
|
ULONG& events) noexcept = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual HRESULT GetConsoleInputImpl(IConsoleInputObject& context,
|
[[nodiscard]] virtual HRESULT GetConsoleInputImpl(IConsoleInputObject& context,
|
||||||
InputEventQueue& outEvents,
|
INPUT_RECORD* outEvents,
|
||||||
const size_t eventReadCount,
|
size_t* eventReadCount,
|
||||||
INPUT_READ_HANDLE_DATA& readHandleState,
|
INPUT_READ_HANDLE_DATA& readHandleState,
|
||||||
const bool IsUnicode,
|
const bool IsUnicode,
|
||||||
const bool IsPeek,
|
const bool IsPeek,
|
||||||
|
|||||||
@@ -215,8 +215,7 @@ INPUT_READ_HANDLE_DATA* ConsoleHandleData::GetClientInput() const
|
|||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - This routine closes an input handle. It decrements the input buffer's
|
// - This routine closes an input handle. It decrements the input buffer's
|
||||||
// reference count. If it goes to zero, the buffer is reinitialized.
|
// reference count.
|
||||||
// Otherwise, the handle is removed from sharing.
|
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// - <none>
|
// - <none>
|
||||||
// Return Value:
|
// Return Value:
|
||||||
@@ -244,11 +243,6 @@ INPUT_READ_HANDLE_DATA* ConsoleHandleData::GetClientInput() const
|
|||||||
// TODO: MSFT: 9115192 - THIS IS BAD. It should use a destructor.
|
// TODO: MSFT: 9115192 - THIS IS BAD. It should use a destructor.
|
||||||
LOG_IF_FAILED(pInputBuffer->FreeIoHandle(this));
|
LOG_IF_FAILED(pInputBuffer->FreeIoHandle(this));
|
||||||
|
|
||||||
if (!pInputBuffer->HasAnyOpenHandles())
|
|
||||||
{
|
|
||||||
pInputBuffer->ReinitializeInputBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,18 +65,8 @@ bool InteractDispatch::WriteCtrlKey(const INPUT_RECORD& event)
|
|||||||
// - True.
|
// - True.
|
||||||
bool InteractDispatch::WriteString(const std::wstring_view string)
|
bool InteractDispatch::WriteString(const std::wstring_view string)
|
||||||
{
|
{
|
||||||
if (!string.empty())
|
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||||
{
|
gci.GetActiveInputBuffer()->Write(string);
|
||||||
const auto codepage = _api.GetConsoleOutputCP();
|
|
||||||
InputEventQueue keyEvents;
|
|
||||||
|
|
||||||
for (const auto& wch : string)
|
|
||||||
{
|
|
||||||
CharToKeyEvents(wch, codepage, keyEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteInput(keyEvents);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -245,14 +245,7 @@ bool InputStateMachineEngine::ActionExecuteFromEscape(const wchar_t wch)
|
|||||||
// - true iff we successfully dispatched the sequence.
|
// - true iff we successfully dispatched the sequence.
|
||||||
bool InputStateMachineEngine::ActionPrint(const wchar_t wch)
|
bool InputStateMachineEngine::ActionPrint(const wchar_t wch)
|
||||||
{
|
{
|
||||||
short vkey = 0;
|
return _pDispatch->WriteString({ &wch, 1 });
|
||||||
DWORD modifierState = 0;
|
|
||||||
auto success = _GenerateKeyFromChar(wch, vkey, modifierState);
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
success = _WriteSingleKey(wch, vkey, modifierState);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
@@ -282,17 +275,7 @@ bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view st
|
|||||||
{
|
{
|
||||||
if (_pDispatch->IsVtInputEnabled())
|
if (_pDispatch->IsVtInputEnabled())
|
||||||
{
|
{
|
||||||
// Synthesize string into key events that we'll write to the buffer
|
return _pDispatch->WriteInput(string);
|
||||||
// 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 ActionPrintString(string);
|
return ActionPrintString(string);
|
||||||
}
|
}
|
||||||
@@ -330,8 +313,7 @@ bool InputStateMachineEngine::ActionEscDispatch(const VTID id)
|
|||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
// Alt is definitely pressed in the esc+key case.
|
// 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);
|
success = _WriteSingleKey(wch, vk, modifierState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user