Replace IInputEvent with INPUT_RECORD (#15673)

`IInputEvent` makes adding Unicode support to `InputBuffer` more
difficult than necessary as the abstract class makes downcasting
as well as copying quite verbose. I found that using `INPUT_RECORD`s
directly leads to a significantly simplified implementation.

In addition, this commit fixes at least one bug: The previous approach
to detect the null key via `DoActiveModifierKeysMatch` didn't work.
As it compared the modifier keys as a bitset with `==` it failed to
match whenever the numpad key was set, which it usually is.

## Validation Steps Performed
* Unit and feature tests are 
This commit is contained in:
Leonard Hecker 2023-08-11 16:06:08 +02:00 committed by GitHub
parent e9c8391fd5
commit 5b44476048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 790 additions and 2282 deletions

View File

@ -511,7 +511,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// modifier key. We'll wait for a real keystroke to dismiss the
// GH #7395 - don't update selection when taking PrintScreen
// selection.
return HasSelection() && !KeyEvent::IsModifierKey(vkey) && vkey != VK_SNAPSHOT;
return HasSelection() && ::Microsoft::Terminal::Core::Terminal::IsInputKey(vkey);
}
bool ControlCore::TryMarkModeKeybinding(const WORD vkey,

View File

@ -661,7 +661,7 @@ bool Terminal::SendKeyEvent(const WORD vkey,
// modifier key. We'll wait for a real keystroke to snap to the bottom.
// GH#6481 - Additionally, make sure the key was actually pressed. This
// check will make sure we behave the same as before GH#6309
if (!KeyEvent::IsModifierKey(vkey) && keyDown)
if (IsInputKey(vkey) && keyDown)
{
TrySnapOnInput();
}
@ -714,8 +714,8 @@ bool Terminal::SendKeyEvent(const WORD vkey,
return false;
}
const KeyEvent keyEv{ keyDown, 1, vkey, sc, ch, states.Value() };
return _handleTerminalInputResult(_terminalInput.HandleKey(&keyEv));
const auto keyEv = SynthesizeKeyEvent(keyDown, 1, vkey, sc, ch, states.Value());
return _handleTerminalInputResult(_terminalInput.HandleKey(keyEv));
}
// Method Description:
@ -791,8 +791,8 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro
MarkOutputStart();
}
const KeyEvent keyDown{ true, 1, vkey, scanCode, ch, states.Value() };
return _handleTerminalInputResult(_terminalInput.HandleKey(&keyDown));
const auto keyDown = SynthesizeKeyEvent(true, 1, vkey, scanCode, ch, states.Value());
return _handleTerminalInputResult(_terminalInput.HandleKey(keyDown));
}
// Method Description:

View File

@ -60,6 +60,22 @@ class Microsoft::Terminal::Core::Terminal final :
using RenderSettings = Microsoft::Console::Render::RenderSettings;
public:
static constexpr bool IsInputKey(WORD vkey)
{
return vkey != VK_CONTROL &&
vkey != VK_LCONTROL &&
vkey != VK_RCONTROL &&
vkey != VK_MENU &&
vkey != VK_LMENU &&
vkey != VK_RMENU &&
vkey != VK_SHIFT &&
vkey != VK_LSHIFT &&
vkey != VK_RSHIFT &&
vkey != VK_LWIN &&
vkey != VK_RWIN &&
vkey != VK_SNAPSHOT;
}
Terminal();
void Create(til::size viewportSize,

View File

@ -87,4 +87,6 @@ void RedrawCommandLine(COOKED_READ_DATA& cookedReadData);
bool IsWordDelim(const wchar_t wch);
bool IsWordDelim(const std::wstring_view charData);
bool IsValidStringBuffer(_In_ bool Unicode, _In_reads_bytes_(Size) PVOID Buffer, _In_ ULONG Size, _In_ ULONG Count, ...);
void SetCurrentCommandLine(COOKED_READ_DATA& cookedReadData, _In_ CommandHistory::Index Index);

View File

@ -462,18 +462,13 @@ void ConsoleImeInfo::_InsertConvertedString(const std::wstring_view text)
}
const auto dwControlKeyState = GetControlKeyState(0);
std::deque<std::unique_ptr<IInputEvent>> inEvents;
KeyEvent keyEvent{ TRUE, // keydown
1, // repeatCount
0, // virtualKeyCode
0, // virtualScanCode
0, // charData
dwControlKeyState }; // activeModifierKeys
InputEventQueue inEvents;
auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, 0, dwControlKeyState);
for (const auto& ch : text)
{
keyEvent.SetCharData(ch);
inEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
keyEvent.Event.KeyEvent.uChar.UnicodeChar = ch;
inEvents.push_back(keyEvent);
}
gci.pInputBuffer->Write(inEvents);

View File

@ -101,7 +101,7 @@ using Microsoft::Console::Interactivity::ServiceLocator;
// Return Value:
// - HRESULT indicating success or failure
[[nodiscard]] static HRESULT _WriteConsoleInputWImplHelper(InputBuffer& context,
std::deque<std::unique_ptr<IInputEvent>>& events,
const std::span<const INPUT_RECORD>& events,
size_t& written,
const bool append) noexcept
{
@ -151,7 +151,7 @@ try
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
til::small_vector<INPUT_RECORD, 16> events;
InputEventQueue events;
auto it = buffer.begin();
const auto end = buffer.end();
@ -160,7 +160,7 @@ try
// the next call to WriteConsoleInputAImpl to join it with the now available trailing DBCS.
if (context.IsWritePartialByteSequenceAvailable())
{
auto lead = context.FetchWritePartialByteSequence(false)->ToInputRecord();
auto lead = context.FetchWritePartialByteSequence();
const auto& trail = *it;
if (trail.EventType == KEY_EVENT)
@ -200,7 +200,7 @@ try
if (it == end)
{
// Missing trailing DBCS -> Store the lead for the next call to WriteConsoleInputAImpl.
context.StoreWritePartialByteSequence(IInputEvent::Create(lead));
context.StoreWritePartialByteSequence(lead);
break;
}
@ -225,8 +225,7 @@ try
}
}
auto result = IInputEvent::Create(std::span{ events.data(), events.size() });
return _WriteConsoleInputWImplHelper(context, result, written, append);
return _WriteConsoleInputWImplHelper(context, events, written, append);
}
CATCH_RETURN();
@ -252,9 +251,7 @@ CATCH_RETURN();
try
{
auto events = IInputEvent::Create(buffer);
return _WriteConsoleInputWImplHelper(context, events, written, append);
return _WriteConsoleInputWImplHelper(context, buffer, written, append);
}
CATCH_RETURN();
}

View File

@ -1919,11 +1919,11 @@ void DbcsTests::TestMultibyteInputCoalescing()
DWORD count;
{
const auto record = KeyEvent{ true, 1, 123, 456, 0x82, 789 }.ToInputRecord();
const auto record = SynthesizeKeyEvent(true, 1, 123, 456, 0x82, 789);
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputA(in, &record, 1, &count));
}
{
const auto record = KeyEvent{ true, 1, 234, 567, 0xA2, 890 }.ToInputRecord();
const auto record = SynthesizeKeyEvent(true, 1, 234, 567, 0xA2, 890);
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputA(in, &record, 1, &count));
}
@ -1933,7 +1933,7 @@ void DbcsTests::TestMultibyteInputCoalescing()
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInputW(in, &actual[0], 2, &count));
VERIFY_ARE_EQUAL(1u, count);
const auto expected = KeyEvent{ true, 1, 123, 456, L'', 789 }.ToInputRecord();
const auto expected = SynthesizeKeyEvent(true, 1, 123, 456, L'', 789);
VERIFY_ARE_EQUAL(expected, actual[0]);
}

View File

@ -86,7 +86,6 @@ class KeyPressTests
expectedRecord.Event.KeyEvent.uChar.UnicodeChar = 0x0;
expectedRecord.Event.KeyEvent.bKeyDown = true;
expectedRecord.Event.KeyEvent.dwControlKeyState = ENHANCED_KEY;
expectedRecord.Event.KeyEvent.dwControlKeyState |= (GetKeyState(VK_NUMLOCK) & KEY_STATE_TOGGLED) ? NUMLOCK_ON : 0;
expectedRecord.Event.KeyEvent.wRepeatCount = SINGLE_KEY_REPEAT;
expectedRecord.Event.KeyEvent.wVirtualKeyCode = VK_APPS;
expectedRecord.Event.KeyEvent.wVirtualScanCode = (WORD)scanCode;

View File

@ -105,17 +105,18 @@ bool ShouldTakeOverKeyboardShortcuts()
// Routine Description:
// - handles key events without reference to Win32 elements.
void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak)
void HandleGenericKeyEvent(INPUT_RECORD event, const bool generateBreak)
{
auto& keyEvent = event.Event.KeyEvent;
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto ContinueProcessing = true;
if (keyEvent.IsCtrlPressed() &&
!keyEvent.IsAltPressed() &&
keyEvent.IsKeyDown())
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED) &&
WI_AreAllFlagsClear(keyEvent.dwControlKeyState, ALT_PRESSED) &&
keyEvent.bKeyDown)
{
// check for ctrl-c, if in line input mode.
if (keyEvent.GetVirtualKeyCode() == 'C' && IsInProcessedInputMode())
if (keyEvent.wVirtualKeyCode == 'C' && IsInProcessedInputMode())
{
HandleCtrlEvent(CTRL_C_EVENT);
if (gci.PopupCount == 0)
@ -130,7 +131,7 @@ void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak)
}
// Check for ctrl-break.
else if (keyEvent.GetVirtualKeyCode() == VK_CANCEL)
else if (keyEvent.wVirtualKeyCode == VK_CANCEL)
{
gci.pInputBuffer->Flush();
HandleCtrlEvent(CTRL_BREAK_EVENT);
@ -146,33 +147,25 @@ void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak)
}
// don't write ctrl-esc to the input buffer
else if (keyEvent.GetVirtualKeyCode() == VK_ESCAPE)
else if (keyEvent.wVirtualKeyCode == VK_ESCAPE)
{
ContinueProcessing = false;
}
}
else if (keyEvent.IsAltPressed() &&
keyEvent.IsKeyDown() &&
keyEvent.GetVirtualKeyCode() == VK_ESCAPE)
else if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) &&
keyEvent.bKeyDown &&
keyEvent.wVirtualKeyCode == VK_ESCAPE)
{
ContinueProcessing = false;
}
if (ContinueProcessing)
{
size_t EventsWritten = 0;
try
gci.pInputBuffer->Write(event);
if (generateBreak)
{
EventsWritten = gci.pInputBuffer->Write(std::make_unique<KeyEvent>(keyEvent));
if (EventsWritten && generateBreak)
{
keyEvent.SetKeyDown(false);
EventsWritten = gci.pInputBuffer->Write(std::make_unique<KeyEvent>(keyEvent));
}
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
keyEvent.bKeyDown = false;
gci.pInputBuffer->Write(event);
}
}
}
@ -210,7 +203,7 @@ void HandleMenuEvent(const DWORD wParam)
size_t EventsWritten = 0;
try
{
EventsWritten = gci.pInputBuffer->Write(std::make_unique<MenuEvent>(wParam));
EventsWritten = gci.pInputBuffer->Write(SynthesizeMenuEvent(wParam));
if (EventsWritten != 1)
{
RIPMSG0(RIP_WARNING, "PutInputInBuffer: EventsWritten != 1, 1 expected");

View File

@ -78,7 +78,7 @@ bool ShouldTakeOverKeyboardShortcuts();
void HandleMenuEvent(const DWORD wParam);
void HandleFocusEvent(const BOOL fSetFocus);
void HandleCtrlEvent(const DWORD EventType);
void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak);
void HandleGenericKeyEvent(INPUT_RECORD event, const bool generateBreak);
void ProcessCtrlEvents();

View File

@ -183,7 +183,7 @@ size_t InputBuffer::PeekCached(bool isUnicode, size_t count, InputEventQueue& ta
break;
}
target.push_back(IInputEvent::Create(e->ToInputRecord()));
target.push_back(e);
i++;
}
@ -222,7 +222,7 @@ void InputBuffer::_switchReadingModeSlowPath(ReadingMode mode)
_cachedTextW = std::wstring{};
_cachedTextReaderW = {};
_cachedInputEvents = std::deque<std::unique_ptr<IInputEvent>>{};
_cachedInputEvents = std::deque<INPUT_RECORD>{};
_readingMode = mode;
}
@ -234,9 +234,9 @@ void InputBuffer::_switchReadingModeSlowPath(ReadingMode mode)
// - None
// Return Value:
// - true if partial char data is available, false otherwise
bool InputBuffer::IsWritePartialByteSequenceAvailable()
bool InputBuffer::IsWritePartialByteSequenceAvailable() const noexcept
{
return _writePartialByteSequence.get() != nullptr;
return _writePartialByteSequenceAvailable;
}
// Routine Description:
@ -245,23 +245,10 @@ bool InputBuffer::IsWritePartialByteSequenceAvailable()
// - peek - if true, data will not be removed after being fetched
// Return Value:
// - the partial char data. may be nullptr if no data is available
std::unique_ptr<IInputEvent> InputBuffer::FetchWritePartialByteSequence(_In_ bool peek)
const INPUT_RECORD& InputBuffer::FetchWritePartialByteSequence() noexcept
{
if (!IsWritePartialByteSequenceAvailable())
{
return std::unique_ptr<IInputEvent>();
}
if (peek)
{
return IInputEvent::Create(_writePartialByteSequence->ToInputRecord());
}
else
{
std::unique_ptr<IInputEvent> outEvent;
outEvent.swap(_writePartialByteSequence);
return outEvent;
}
_writePartialByteSequenceAvailable = false;
return _writePartialByteSequence;
}
// Routine Description:
@ -271,9 +258,10 @@ std::unique_ptr<IInputEvent> InputBuffer::FetchWritePartialByteSequence(_In_ boo
// - event - The event to store
// Return Value:
// - None
void InputBuffer::StoreWritePartialByteSequence(std::unique_ptr<IInputEvent> event)
void InputBuffer::StoreWritePartialByteSequence(const INPUT_RECORD& event) noexcept
{
_writePartialByteSequence.swap(event);
_writePartialByteSequenceAvailable = true;
_writePartialByteSequence = event;
}
// Routine Description:
@ -348,8 +336,8 @@ 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 std::unique_ptr<IInputEvent>& event) {
return event->EventType() != InputEventType::KeyEvent;
auto newEnd = std::remove_if(_storage.begin(), _storage.end(), [](const INPUT_RECORD& event) {
return event.EventType != KEY_EVENT;
});
_storage.erase(newEnd, _storage.end());
}
@ -391,7 +379,7 @@ void InputBuffer::PassThroughWin32MouseRequest(bool enable)
// - STATUS_SUCCESS if records were read into the client buffer and everything is OK.
// - CONSOLE_STATUS_WAIT if there weren't enough records to satisfy the request (and waits are allowed)
// - otherwise a suitable memory/math/string error in NTSTATUS form.
[[nodiscard]] NTSTATUS InputBuffer::Read(_Out_ std::deque<std::unique_ptr<IInputEvent>>& OutEvents,
[[nodiscard]] NTSTATUS InputBuffer::Read(_Out_ InputEventQueue& OutEvents,
const size_t AmountToRead,
const bool Peek,
const bool WaitForData,
@ -417,31 +405,29 @@ try
while (it != end && OutEvents.size() < AmountToRead)
{
auto event = IInputEvent::Create((*it)->ToInputRecord());
if (event->EventType() == InputEventType::KeyEvent)
if (it->EventType == KEY_EVENT)
{
const auto keyEvent = static_cast<KeyEvent*>(event.get());
auto event = *it;
WORD repeat = 1;
// for stream reads we need to split any key events that have been coalesced
if (Stream)
{
repeat = keyEvent->GetRepeatCount();
keyEvent->SetRepeatCount(1);
repeat = std::max<WORD>(1, event.Event.KeyEvent.wRepeatCount);
event.Event.KeyEvent.wRepeatCount = 1;
}
if (Unicode)
{
do
{
OutEvents.push_back(std::make_unique<KeyEvent>(*keyEvent));
OutEvents.push_back(event);
repeat--;
} while (repeat > 0 && OutEvents.size() < AmountToRead);
}
else
{
const auto wch = keyEvent->GetCharData();
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);
@ -453,9 +439,10 @@ try
{
for (const auto& ch : str)
{
auto tempEvent = std::make_unique<KeyEvent>(*keyEvent);
tempEvent->SetCharData(ch);
OutEvents.push_back(std::move(tempEvent));
// 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);
}
repeat--;
} while (repeat > 0 && OutEvents.size() < AmountToRead);
@ -463,14 +450,13 @@ try
if (repeat && !Peek)
{
const auto originalKeyEvent = static_cast<KeyEvent*>((*it).get());
originalKeyEvent->SetRepeatCount(repeat);
it->Event.KeyEvent.wRepeatCount = repeat;
break;
}
}
else
{
OutEvents.push_back(std::move(event));
OutEvents.push_back(*it);
}
++it;
@ -498,51 +484,6 @@ catch (...)
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
// Routine Description:
// - This routine reads a single event from the input buffer.
// - It can convert returned data to through the currently set Input CP, it can optionally return a wait condition
// if there isn't enough data in the buffer, and it can be set to not remove records as it reads them out.
// Note:
// - The console lock must be held when calling this routine.
// Arguments:
// - outEvent - where the read event is stored
// - Peek - If true, copy events to pInputRecord but don't remove them from the input buffer.
// - WaitForData - if true, wait until an event is input (if there aren't enough to fill client buffer). if false, return immediately
// - Unicode - true if the data in key events should be treated as unicode. false if they should be converted by the current input CP.
// - Stream - true if read should unpack KeyEvents that have a >1 repeat count.
// Return Value:
// - STATUS_SUCCESS if records were read into the client buffer and everything is OK.
// - CONSOLE_STATUS_WAIT if there weren't enough records to satisfy the request (and waits are allowed)
// - otherwise a suitable memory/math/string error in NTSTATUS form.
[[nodiscard]] NTSTATUS InputBuffer::Read(_Out_ std::unique_ptr<IInputEvent>& outEvent,
const bool Peek,
const bool WaitForData,
const bool Unicode,
const bool Stream)
{
NTSTATUS Status;
try
{
std::deque<std::unique_ptr<IInputEvent>> outEvents;
Status = Read(outEvents,
1,
Peek,
WaitForData,
Unicode,
Stream);
if (!outEvents.empty())
{
outEvent.swap(outEvents.front());
}
}
catch (...)
{
Status = NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
return Status;
}
// Routine Description:
// - Writes events to the beginning of the input buffer.
// Arguments:
@ -552,7 +493,7 @@ catch (...)
// S_OK if successful.
// Note:
// - The console lock must be held when calling this routine.
size_t InputBuffer::Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
size_t InputBuffer::Prepend(const std::span<const INPUT_RECORD>& inEvents)
{
try
{
@ -569,7 +510,7 @@ size_t InputBuffer::Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& in
// this way to handle any coalescing that might occur.
// get all of the existing records, "emptying" the buffer
std::deque<std::unique_ptr<IInputEvent>> existingStorage;
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.
@ -583,10 +524,10 @@ size_t InputBuffer::Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& in
_WriteBuffer(inEvents, prependEventsWritten, unusedWaitStatus);
FAIL_FAST_IF(!(unusedWaitStatus));
// write all previously existing records
size_t existingEventsWritten;
_WriteBuffer(existingStorage, existingEventsWritten, 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.
@ -621,19 +562,9 @@ size_t InputBuffer::Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& in
// - The console lock must be held when calling this routine.
// - any outside references to inEvent will ben invalidated after
// calling this method.
size_t InputBuffer::Write(_Inout_ std::unique_ptr<IInputEvent> inEvent)
size_t InputBuffer::Write(const INPUT_RECORD& inEvent)
{
try
{
std::deque<std::unique_ptr<IInputEvent>> inEvents;
inEvents.push_back(std::move(inEvent));
return Write(inEvents);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return 0;
}
return Write(std::span{ &inEvent, 1 });
}
// Routine Description:
@ -645,7 +576,7 @@ size_t InputBuffer::Write(_Inout_ std::unique_ptr<IInputEvent> inEvent)
// - The number of events that were written to input buffer.
// Note:
// - The console lock must be held when calling this routine.
size_t InputBuffer::Write(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
size_t InputBuffer::Write(const std::span<const INPUT_RECORD>& inEvents)
{
try
{
@ -694,7 +625,7 @@ void InputBuffer::WriteFocusEvent(bool focused) noexcept
{
// This is a mini-version of Write().
const auto wasEmpty = _storage.empty();
_storage.push_back(std::make_unique<FocusEvent>(focused));
_storage.push_back(SynthesizeFocusEvent(focused));
if (wasEmpty)
{
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
@ -760,9 +691,7 @@ static bool IsPauseKey(const KEY_EVENT_RECORD& event)
// Note:
// - The console lock must be held when calling this routine.
// - will throw on failure
void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents,
_Out_ size_t& eventsWritten,
_Out_ bool& setWaitEvent)
void InputBuffer::_WriteBuffer(const std::span<const INPUT_RECORD>& inEvents, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
@ -772,18 +701,18 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
const auto initialInEventsSize = inEvents.size();
const auto vtInputMode = IsInVirtualTerminalInputMode();
for (auto& inEvent : inEvents)
for (const auto& inEvent : inEvents)
{
if (inEvent->EventType() == InputEventType::KeyEvent && static_cast<const KeyEvent*>(inEvent.get())->IsKeyDown())
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(static_cast<const KeyEvent*>(inEvent.get())->GetVirtualKeyCode()))
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->ToInputRecord().Event.KeyEvent))
if (WI_IsFlagSet(InputMode, ENABLE_LINE_INPUT) && IsPauseKey(inEvent.Event.KeyEvent))
{
WI_SetFlag(gci.Flags, CONSOLE_SUSPENDED);
continue;
@ -797,7 +726,7 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
if (vtInputMode)
{
// GH#11682: TerminalInput::HandleKey can handle both KeyEvents and Focus events seamlessly
if (const auto out = _termInput.HandleKey(inEvent.get()))
if (const auto out = _termInput.HandleKey(inEvent))
{
_HandleTerminalInputCallback(*out);
eventsWritten++;
@ -816,7 +745,7 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
}
// At this point, the event was neither coalesced, nor processed by VT.
_storage.push_back(std::move(inEvent));
_storage.push_back(inEvent);
++eventsWritten;
}
if (initiallyEmptyQueue && !_storage.empty())
@ -839,39 +768,39 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
// 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 std::unique_ptr<IInputEvent>& inEvent) const noexcept
bool InputBuffer::_CoalesceEvent(const INPUT_RECORD& inEvent) noexcept
{
auto& lastEvent = _storage.back();
if (lastEvent->EventType() == InputEventType::MouseEvent && inEvent->EventType() == InputEventType::MouseEvent)
if (lastEvent.EventType == MOUSE_EVENT && inEvent.EventType == MOUSE_EVENT)
{
const auto& inMouse = *static_cast<const MouseEvent*>(inEvent.get());
auto& lastMouse = *static_cast<MouseEvent*>(lastEvent.get());
const auto& inMouse = inEvent.Event.MouseEvent;
auto& lastMouse = lastEvent.Event.MouseEvent;
if (lastMouse.IsMouseMoveEvent() && inMouse.IsMouseMoveEvent())
if (lastMouse.dwEventFlags == MOUSE_MOVED && inMouse.dwEventFlags == MOUSE_MOVED)
{
lastMouse.SetPosition(inMouse.GetPosition());
lastMouse.dwMousePosition = inMouse.dwMousePosition;
return true;
}
}
else if (lastEvent->EventType() == InputEventType::KeyEvent && inEvent->EventType() == InputEventType::KeyEvent)
else if (lastEvent.EventType == KEY_EVENT && inEvent.EventType == KEY_EVENT)
{
const auto& inKey = *static_cast<const KeyEvent*>(inEvent.get());
auto& lastKey = *static_cast<KeyEvent*>(lastEvent.get());
const auto& inKey = inEvent.Event.KeyEvent;
auto& lastKey = lastEvent.Event.KeyEvent;
if (lastKey.IsKeyDown() && inKey.IsKeyDown() &&
(lastKey.GetVirtualScanCode() == inKey.GetVirtualScanCode() || WI_IsFlagSet(inKey.GetActiveModifierKeys(), NLS_IME_CONVERSION)) &&
lastKey.GetCharData() == inKey.GetCharData() &&
lastKey.GetActiveModifierKeys() == inKey.GetActiveModifierKeys() &&
// TODO: This behavior is an import from old conhost v1 and has been broken for decades.
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.GetCharData()))
!IsGlyphFullWidth(inKey.uChar.UnicodeChar))
{
lastKey.SetRepeatCount(lastKey.GetRepeatCount() + inKey.GetRepeatCount());
lastKey.wRepeatCount += inKey.wRepeatCount;
return true;
}
}
@ -908,7 +837,7 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType&
for (const auto& wch : text)
{
_storage.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
_storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
}
if (!_vtInputShouldSuppress)

View File

@ -1,20 +1,5 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- inputBuffer.hpp
Abstract:
- storage area for incoming input events.
Author:
- Therese Stowell (Thereses) 12-Nov-1990. Adapted from OS/2 subsystem server\srvpipe.c
Revision History:
- Moved from input.h/input.cpp. (AustDi, 2017)
- Refactored to class, added stl container usage (AustDi, 2017)
--*/
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
@ -52,9 +37,9 @@ public:
void Cache(bool isUnicode, InputEventQueue& source, size_t expectedSourceSize);
// storage API for partial dbcs bytes being written to the buffer
bool IsWritePartialByteSequenceAvailable();
std::unique_ptr<IInputEvent> FetchWritePartialByteSequence(_In_ bool peek);
void StoreWritePartialByteSequence(std::unique_ptr<IInputEvent> event);
bool IsWritePartialByteSequenceAvailable() const noexcept;
const INPUT_RECORD& FetchWritePartialByteSequence() noexcept;
void StoreWritePartialByteSequence(const INPUT_RECORD& event) noexcept;
void ReinitializeInputBuffer();
void WakeUpReadersWaitingForData();
@ -63,24 +48,16 @@ public:
void Flush();
void FlushAllButKeys();
[[nodiscard]] NTSTATUS Read(_Out_ std::deque<std::unique_ptr<IInputEvent>>& OutEvents,
[[nodiscard]] NTSTATUS Read(_Out_ InputEventQueue& OutEvents,
const size_t AmountToRead,
const bool Peek,
const bool WaitForData,
const bool Unicode,
const bool Stream);
[[nodiscard]] NTSTATUS Read(_Out_ std::unique_ptr<IInputEvent>& inEvent,
const bool Peek,
const bool WaitForData,
const bool Unicode,
const bool Stream);
size_t Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents);
size_t Write(_Inout_ std::unique_ptr<IInputEvent> inEvent);
size_t Write(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents);
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 WriteFocusEvent(bool focused) noexcept;
bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta);
@ -102,11 +79,12 @@ private:
std::string_view _cachedTextReaderA;
std::wstring _cachedTextW;
std::wstring_view _cachedTextReaderW;
std::deque<std::unique_ptr<IInputEvent>> _cachedInputEvents;
std::deque<INPUT_RECORD> _cachedInputEvents;
ReadingMode _readingMode = ReadingMode::StringA;
std::deque<std::unique_ptr<IInputEvent>> _storage;
std::unique_ptr<IInputEvent> _writePartialByteSequence;
std::deque<INPUT_RECORD> _storage;
INPUT_RECORD _writePartialByteSequence{};
bool _writePartialByteSequenceAvailable = false;
Microsoft::Console::VirtualTerminal::TerminalInput _termInput;
Microsoft::Console::Render::VtEngine* _pTtyConnection;
@ -118,12 +96,8 @@ private:
void _switchReadingMode(ReadingMode mode);
void _switchReadingModeSlowPath(ReadingMode mode);
void _WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inRecords,
_Out_ size_t& eventsWritten,
_Out_ bool& setWaitEvent);
bool _CoalesceEvent(const std::unique_ptr<IInputEvent>& inEvent) const noexcept;
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);
#ifdef UNIT_TESTING

View File

@ -257,7 +257,7 @@ void ScreenBufferSizeChange(const til::size coordNewSize)
try
{
gci.pInputBuffer->Write(std::make_unique<WindowBufferSizeEvent>(coordNewSize));
gci.pInputBuffer->Write(SynthesizeWindowBufferSizeEvent(coordNewSize));
}
catch (...)
{

View File

@ -33,18 +33,17 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) :
// - <none>
void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response)
{
std::deque<std::unique_ptr<IInputEvent>> inEvents;
InputEventQueue inEvents;
// generate a paired key down and key up event for every
// character to be sent into the console's input buffer
for (const auto& wch : response)
{
// This wasn't from a real keyboard, so we're leaving key/scan codes blank.
KeyEvent keyEvent{ TRUE, 1, 0, 0, wch, 0 };
inEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
keyEvent.SetKeyDown(false);
inEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, wch, 0);
inEvents.push_back(keyEvent);
keyEvent.Event.KeyEvent.bKeyDown = false;
inEvents.push_back(keyEvent);
}
// TODO GH#4954 During the input refactor we may want to add a "priority" input list

View File

@ -47,7 +47,7 @@ DirectReadData::DirectReadData(_In_ InputBuffer* const pInputBuffer,
// - pControlKeyState - For certain types of reads, this specifies
// which modifier keys were held.
// - pOutputData - a pointer to a
// std::deque<std::unique_ptr<IInputEvent>> that is used to the read
// InputEventQueue that is used to the read
// input events back to the server
// Return Value:
// - true if the wait is done and result buffer/status code can be sent back to the client.
@ -69,8 +69,6 @@ try
*pControlKeyState = 0;
*pNumBytes = 0;
std::deque<std::unique_ptr<IInputEvent>> readEvents;
// If ctrl-c or ctrl-break was seen, ignore it.
if (WI_IsAnyFlagSet(TerminationReason, (WaitTerminationReason::CtrlC | WaitTerminationReason::CtrlBreak)))
{
@ -119,7 +117,7 @@ try
}
// move events to pOutputData
const auto pOutputDeque = static_cast<std::deque<std::unique_ptr<IInputEvent>>* const>(pOutputData);
const auto pOutputDeque = static_cast<InputEventQueue* const>(pOutputData);
*pNumBytes = _outEvents.size() * sizeof(INPUT_RECORD);
*pOutputDeque = std::move(_outEvents);

View File

@ -47,5 +47,5 @@ public:
private:
const size_t _eventReadCount;
std::deque<std::unique_ptr<IInputEvent>> _outEvents;
InputEventQueue _outEvents;
};

View File

@ -16,10 +16,95 @@
#include "../interactivity/inc/ServiceLocator.hpp"
#pragma hdrstop
using Microsoft::Console::Interactivity::ServiceLocator;
static bool IsCommandLinePopupKey(const KEY_EVENT_RECORD& event)
{
if (WI_AreAllFlagsClear(event.dwControlKeyState, RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
{
switch (event.wVirtualKeyCode)
{
case VK_ESCAPE:
case VK_PRIOR:
case VK_NEXT:
case VK_END:
case VK_HOME:
case VK_LEFT:
case VK_UP:
case VK_RIGHT:
case VK_DOWN:
case VK_F2:
case VK_F4:
case VK_F7:
case VK_F9:
case VK_DELETE:
return true;
default:
break;
}
}
return false;
}
static bool IsCommandLineEditingKey(const KEY_EVENT_RECORD& event)
{
if (WI_AreAllFlagsClear(event.dwControlKeyState, RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
{
switch (event.wVirtualKeyCode)
{
case VK_ESCAPE:
case VK_PRIOR:
case VK_NEXT:
case VK_END:
case VK_HOME:
case VK_LEFT:
case VK_UP:
case VK_RIGHT:
case VK_DOWN:
case VK_INSERT:
case VK_DELETE:
case VK_F1:
case VK_F2:
case VK_F3:
case VK_F4:
case VK_F5:
case VK_F6:
case VK_F7:
case VK_F8:
case VK_F9:
return true;
default:
break;
}
}
if (WI_IsAnyFlagSet(event.dwControlKeyState, RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
{
switch (event.wVirtualKeyCode)
{
case VK_END:
case VK_HOME:
case VK_LEFT:
case VK_RIGHT:
return true;
default:
break;
}
}
if (WI_IsAnyFlagSet(event.dwControlKeyState, RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED))
{
switch (event.wVirtualKeyCode)
{
case VK_F7:
case VK_F10:
return true;
default:
break;
}
}
return false;
}
// Routine Description:
// - This routine is used in stream input. It gets input and filters it for unicode characters.
// Arguments:
@ -56,57 +141,50 @@ using Microsoft::Console::Interactivity::ServiceLocator;
*pdwKeyState = 0;
}
NTSTATUS Status;
for (;;)
{
std::unique_ptr<IInputEvent> inputEvent;
Status = pInputBuffer->Read(inputEvent,
false, // peek
Wait,
true, // unicode
true); // stream
InputEventQueue events;
const auto Status = pInputBuffer->Read(events, 1, false, Wait, true, true);
if (FAILED_NTSTATUS(Status))
{
return Status;
}
else if (inputEvent.get() == nullptr)
if (events.empty())
{
FAIL_FAST_IF(Wait);
assert(!Wait);
return STATUS_UNSUCCESSFUL;
}
if (inputEvent->EventType() == InputEventType::KeyEvent)
const auto& Event = events[0];
if (Event.EventType == KEY_EVENT)
{
auto keyEvent = std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(inputEvent.release()));
auto commandLineEditKey = false;
if (pCommandLineEditingKeys)
{
commandLineEditKey = keyEvent->IsCommandLineEditingKey();
commandLineEditKey = IsCommandLineEditingKey(Event.Event.KeyEvent);
}
else if (pPopupKeys)
{
commandLineEditKey = keyEvent->IsPopupKey();
commandLineEditKey = IsCommandLinePopupKey(Event.Event.KeyEvent);
}
if (pdwKeyState)
{
*pdwKeyState = keyEvent->GetActiveModifierKeys();
*pdwKeyState = Event.Event.KeyEvent.dwControlKeyState;
}
if (keyEvent->GetCharData() != 0 && !commandLineEditKey)
if (Event.Event.KeyEvent.uChar.UnicodeChar != 0 && !commandLineEditKey)
{
// chars that are generated using alt + numpad
if (!keyEvent->IsKeyDown() && keyEvent->GetVirtualKeyCode() == VK_MENU)
if (!Event.Event.KeyEvent.bKeyDown && Event.Event.KeyEvent.wVirtualKeyCode == VK_MENU)
{
if (keyEvent->IsAltNumpadSet())
if (WI_IsFlagSet(Event.Event.KeyEvent.dwControlKeyState, ALTNUMPAD_BIT))
{
if (HIBYTE(keyEvent->GetCharData()))
if (HIBYTE(Event.Event.KeyEvent.uChar.UnicodeChar))
{
char chT[2] = {
static_cast<char>(HIBYTE(keyEvent->GetCharData())),
static_cast<char>(LOBYTE(keyEvent->GetCharData())),
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);
}
@ -115,64 +193,54 @@ using Microsoft::Console::Interactivity::ServiceLocator;
// 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(keyEvent->GetCharData());
char chT = LOBYTE(Event.Event.KeyEvent.uChar.UnicodeChar);
*pwchOut = CharToWchar(&chT, 1);
}
}
else
{
*pwchOut = keyEvent->GetCharData();
*pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
}
return STATUS_SUCCESS;
}
// Ignore Escape and Newline chars
else if (keyEvent->IsKeyDown() &&
(WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT) ||
(keyEvent->GetVirtualKeyCode() != VK_ESCAPE &&
keyEvent->GetCharData() != UNICODE_LINEFEED)))
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 = keyEvent->GetCharData();
*pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
return STATUS_SUCCESS;
}
}
if (keyEvent->IsKeyDown())
if (Event.Event.KeyEvent.bKeyDown)
{
if (pCommandLineEditingKeys && commandLineEditKey)
{
*pCommandLineEditingKeys = true;
*pwchOut = static_cast<wchar_t>(keyEvent->GetVirtualKeyCode());
*pwchOut = static_cast<wchar_t>(Event.Event.KeyEvent.wVirtualKeyCode);
return STATUS_SUCCESS;
}
else if (pPopupKeys && commandLineEditKey)
if (pPopupKeys && commandLineEditKey)
{
*pPopupKeys = true;
*pwchOut = static_cast<char>(keyEvent->GetVirtualKeyCode());
*pwchOut = static_cast<char>(Event.Event.KeyEvent.wVirtualKeyCode);
return STATUS_SUCCESS;
}
else
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))
{
const auto zeroVkeyData = OneCoreSafeVkKeyScanW(0);
const auto zeroVKey = LOBYTE(zeroVkeyData);
const auto zeroControlKeyState = HIBYTE(zeroVkeyData);
try
{
// Convert real Windows NT modifier bit into bizarre Console bits
auto consoleModKeyState = FromVkKeyScan(zeroControlKeyState);
if (zeroVKey == keyEvent->GetVirtualKeyCode() &&
keyEvent->DoActiveModifierKeysMatch(consoleModKeyState))
{
// This really is the character 0x0000
*pwchOut = keyEvent->GetCharData();
return STATUS_SUCCESS;
}
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
}
// This really is the character 0x0000
*pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
return STATUS_SUCCESS;
}
}
}

View File

@ -153,106 +153,45 @@ class ClipboardTests
VERIFY_IS_NOT_NULL(ptr);
}
TEST_METHOD(CanConvertTextToInputEvents)
TEST_METHOD(CanConvertText)
{
std::wstring wstr = L"hello world";
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
wstr.size());
VERIFY_ARE_EQUAL(wstr.size() * 2, events.size());
for (auto wch : wstr)
static constexpr std::wstring_view input{ L"HeLlO WoRlD" };
const auto events = Clipboard::Instance().TextToKeyEvents(input.data(), input.size());
const auto shiftSC = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_SHIFT, MAPVK_VK_TO_VSC));
const auto shiftDown = SynthesizeKeyEvent(true, 1, VK_SHIFT, shiftSC, 0, SHIFT_PRESSED);
const auto shiftUp = SynthesizeKeyEvent(false, 1, VK_SHIFT, shiftSC, 0, 0);
InputEventQueue expectedEvents;
for (auto wch : input)
{
std::deque<bool> keydownPattern{ true, false };
for (auto isKeyDown : keydownPattern)
const auto state = OneCoreSafeVkKeyScanW(wch);
const auto vk = LOBYTE(state);
const auto sc = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
const auto shift = WI_IsFlagSet(state, 0x100);
auto event = SynthesizeKeyEvent(true, 1, vk, sc, wch, shift ? SHIFT_PRESSED : 0);
if (shift)
{
VERIFY_ARE_EQUAL(InputEventType::KeyEvent, events.front()->EventType());
std::unique_ptr<KeyEvent> keyEvent;
keyEvent.reset(static_cast<KeyEvent* const>(events.front().release()));
events.pop_front();
expectedEvents.push_back(shiftDown);
}
const auto keyState = OneCoreSafeVkKeyScanW(wch);
VERIFY_ARE_NOT_EQUAL(-1, keyState);
const auto virtualScanCode = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(LOBYTE(keyState), MAPVK_VK_TO_VSC));
expectedEvents.push_back(event);
event.Event.KeyEvent.bKeyDown = FALSE;
expectedEvents.push_back(event);
VERIFY_ARE_EQUAL(wch, keyEvent->GetCharData());
VERIFY_ARE_EQUAL(isKeyDown, keyEvent->IsKeyDown());
VERIFY_ARE_EQUAL(1, keyEvent->GetRepeatCount());
VERIFY_ARE_EQUAL(static_cast<DWORD>(0), keyEvent->GetActiveModifierKeys());
VERIFY_ARE_EQUAL(virtualScanCode, keyEvent->GetVirtualScanCode());
VERIFY_ARE_EQUAL(LOBYTE(keyState), keyEvent->GetVirtualKeyCode());
if (shift)
{
expectedEvents.push_back(shiftUp);
}
}
}
TEST_METHOD(CanConvertUppercaseText)
{
std::wstring wstr = L"HeLlO WoRlD";
size_t uppercaseCount = 0;
for (auto wch : wstr)
VERIFY_ARE_EQUAL(expectedEvents.size(), events.size());
for (size_t i = 0; i < events.size(); ++i)
{
std::isupper(wch) ? ++uppercaseCount : 0;
}
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
wstr.size());
VERIFY_ARE_EQUAL((wstr.size() + uppercaseCount) * 2, events.size());
for (auto wch : wstr)
{
std::deque<bool> keydownPattern{ true, false };
for (auto isKeyDown : keydownPattern)
{
Log::Comment(NoThrowString().Format(L"testing char: %C; keydown: %d", wch, isKeyDown));
VERIFY_ARE_EQUAL(InputEventType::KeyEvent, events.front()->EventType());
std::unique_ptr<KeyEvent> keyEvent;
keyEvent.reset(static_cast<KeyEvent* const>(events.front().release()));
events.pop_front();
const short keyScanError = -1;
const auto keyState = OneCoreSafeVkKeyScanW(wch);
VERIFY_ARE_NOT_EQUAL(keyScanError, keyState);
const auto virtualScanCode = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(LOBYTE(keyState), MAPVK_VK_TO_VSC));
if (std::isupper(wch))
{
// uppercase letters have shift key events
// surrounding them, making two events per letter
// (and another two for the keyup)
VERIFY_IS_FALSE(events.empty());
VERIFY_ARE_EQUAL(InputEventType::KeyEvent, events.front()->EventType());
std::unique_ptr<KeyEvent> keyEvent2;
keyEvent2.reset(static_cast<KeyEvent* const>(events.front().release()));
events.pop_front();
const auto keyState2 = OneCoreSafeVkKeyScanW(wch);
VERIFY_ARE_NOT_EQUAL(keyScanError, keyState2);
const auto virtualScanCode2 = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(LOBYTE(keyState2), MAPVK_VK_TO_VSC));
if (isKeyDown)
{
// shift then letter
const KeyEvent shiftDownEvent{ TRUE, 1, VK_SHIFT, leftShiftScanCode, L'\0', SHIFT_PRESSED };
VERIFY_ARE_EQUAL(shiftDownEvent, *keyEvent);
const KeyEvent expectedKeyEvent{ TRUE, 1, LOBYTE(keyState2), virtualScanCode2, wch, SHIFT_PRESSED };
VERIFY_ARE_EQUAL(expectedKeyEvent, *keyEvent2);
}
else
{
// letter then shift
const KeyEvent expectedKeyEvent{ FALSE, 1, LOBYTE(keyState), virtualScanCode, wch, SHIFT_PRESSED };
VERIFY_ARE_EQUAL(expectedKeyEvent, *keyEvent);
const KeyEvent shiftUpEvent{ FALSE, 1, VK_SHIFT, leftShiftScanCode, L'\0', 0 };
VERIFY_ARE_EQUAL(shiftUpEvent, *keyEvent2);
}
}
else
{
const KeyEvent expectedKeyEvent{ !!isKeyDown, 1, LOBYTE(keyState), virtualScanCode, wch, 0 };
VERIFY_ARE_EQUAL(expectedKeyEvent, *keyEvent);
}
}
VERIFY_ARE_EQUAL(expectedEvents[i], events[i]);
}
}
@ -274,23 +213,22 @@ class ClipboardTests
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
wstr.size());
std::deque<KeyEvent> expectedEvents;
InputEventQueue expectedEvents;
// should be converted to:
// 1. AltGr keydown
// 2. € keydown
// 3. € keyup
// 4. AltGr keyup
expectedEvents.push_back({ TRUE, 1, VK_MENU, altScanCode, L'\0', (ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) });
expectedEvents.push_back({ TRUE, 1, virtualKeyCode, virtualScanCode, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) });
expectedEvents.push_back({ FALSE, 1, virtualKeyCode, virtualScanCode, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) });
expectedEvents.push_back({ FALSE, 1, VK_MENU, altScanCode, L'\0', ENHANCED_KEY });
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, L'\0', (ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)));
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, virtualKeyCode, virtualScanCode, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)));
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, virtualKeyCode, virtualScanCode, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)));
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, L'\0', ENHANCED_KEY));
VERIFY_ARE_EQUAL(expectedEvents.size(), events.size());
for (size_t i = 0; i < events.size(); ++i)
{
const auto currentKeyEvent = *reinterpret_cast<const KeyEvent* const>(events[i].get());
VERIFY_ARE_EQUAL(expectedEvents[i], currentKeyEvent, NoThrowString().Format(L"i == %d", i));
VERIFY_ARE_EQUAL(expectedEvents[i], events[i]);
}
}
@ -302,7 +240,7 @@ class ClipboardTests
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
wstr.size());
std::deque<KeyEvent> expectedEvents;
InputEventQueue expectedEvents;
if constexpr (Feature_UseNumpadEventsForClipboardInput::IsEnabled())
{
// Inside Windows, where numpad events are enabled, this generated numpad events.
@ -313,26 +251,25 @@ class ClipboardTests
// 4. 2nd numpad keydown
// 5. 2nd numpad keyup
// 6. left alt keyup
expectedEvents.push_back({ TRUE, 1, VK_MENU, altScanCode, L'\0', LEFT_ALT_PRESSED });
expectedEvents.push_back({ TRUE, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED });
expectedEvents.push_back({ FALSE, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED });
expectedEvents.push_back({ TRUE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED });
expectedEvents.push_back({ FALSE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED });
expectedEvents.push_back({ FALSE, 1, VK_MENU, altScanCode, wstr[0], 0 });
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, L'\0', LEFT_ALT_PRESSED));
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED));
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED));
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED));
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED));
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, wstr[0], 0));
}
else
{
// Outside Windows, without numpad events, we just emit the key with a nonzero UnicodeChar
expectedEvents.push_back({ TRUE, 1, 0, 0, wstr[0], 0 });
expectedEvents.push_back({ FALSE, 1, 0, 0, wstr[0], 0 });
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wstr[0], 0));
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, 0, 0, wstr[0], 0));
}
VERIFY_ARE_EQUAL(expectedEvents.size(), events.size());
for (size_t i = 0; i < events.size(); ++i)
{
const auto currentKeyEvent = *reinterpret_cast<const KeyEvent* const>(events[i].get());
VERIFY_ARE_EQUAL(expectedEvents[i], currentKeyEvent, NoThrowString().Format(L"i == %d", i));
VERIFY_ARE_EQUAL(expectedEvents[i], events[i]);
}
}
};

View File

@ -39,7 +39,6 @@
<ClCompile Include="VtIoTests.cpp" />
<ClCompile Include="VtRendererTests.cpp" />
<ClCompile Include="ConptyOutputTests.cpp" />
<Clcompile Include="..\..\types\IInputEventStreams.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>

View File

@ -63,9 +63,6 @@
<ClCompile Include="VtRendererTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<Clcompile Include="..\..\types\IInputEventStreams.cpp">
<Filter>Source Files</Filter>
</Clcompile>
<ClCompile Include="AliasTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>

View File

@ -61,12 +61,12 @@ class InputBufferTests
{
InputBuffer inputBuffer;
auto record = MakeKeyEvent(true, 1, L'a', 0, L'a', 0);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 1u);
// add another event, check again
INPUT_RECORD record2;
record2.EventType = MENU_EVENT;
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record2)), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record2), 0u);
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 2u);
}
@ -77,8 +77,8 @@ class InputBufferTests
{
INPUT_RECORD record;
record.EventType = MENU_EVENT;
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
VERIFY_ARE_EQUAL(record, inputBuffer._storage.back()->ToInputRecord());
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
VERIFY_ARE_EQUAL(record, inputBuffer._storage.back());
}
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
}
@ -86,19 +86,19 @@ class InputBufferTests
TEST_METHOD(CanBulkInsertIntoInputBuffer)
{
InputBuffer inputBuffer;
std::deque<std::unique_ptr<IInputEvent>> events;
InputEventQueue events;
INPUT_RECORD record;
record.EventType = MENU_EVENT;
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
events.push_back(IInputEvent::Create(record));
events.push_back(record);
}
VERIFY_IS_GREATER_THAN(inputBuffer.Write(events), 0u);
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
// verify that the events are the same in storage
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_ARE_EQUAL(inputBuffer._storage[i]->ToInputRecord(), record);
VERIFY_ARE_EQUAL(inputBuffer._storage[i], record);
}
}
@ -115,23 +115,22 @@ class InputBufferTests
{
mouseRecord.Event.MouseEvent.dwMousePosition.X = static_cast<SHORT>(i + 1);
mouseRecord.Event.MouseEvent.dwMousePosition.Y = static_cast<SHORT>(i + 1) * 2;
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(mouseRecord)), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(mouseRecord), 0u);
}
// check that they coalesced
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 1u);
// check that the mouse position is being updated correctly
const IInputEvent* const pOutEvent = inputBuffer._storage.front().get();
const auto pMouseEvent = static_cast<const MouseEvent* const>(pOutEvent);
VERIFY_ARE_EQUAL(pMouseEvent->GetPosition().x, static_cast<SHORT>(RECORD_INSERT_COUNT));
VERIFY_ARE_EQUAL(pMouseEvent->GetPosition().y, static_cast<SHORT>(RECORD_INSERT_COUNT * 2));
const auto& pMouseEvent = inputBuffer._storage.front().Event.MouseEvent;
VERIFY_ARE_EQUAL(pMouseEvent.dwMousePosition.X, static_cast<SHORT>(RECORD_INSERT_COUNT));
VERIFY_ARE_EQUAL(pMouseEvent.dwMousePosition.Y, static_cast<SHORT>(RECORD_INSERT_COUNT * 2));
// add a key event and another mouse event to make sure that
// an event between two mouse events stopped the coalescing.
INPUT_RECORD keyRecord;
keyRecord.EventType = KEY_EVENT;
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(keyRecord)), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(mouseRecord)), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(keyRecord), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(mouseRecord), 0u);
// verify
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 3u);
@ -143,29 +142,25 @@ class InputBufferTests
InputBuffer inputBuffer;
INPUT_RECORD mouseRecords[RECORD_INSERT_COUNT];
std::deque<std::unique_ptr<IInputEvent>> events;
InputEventQueue events;
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
mouseRecords[i].EventType = MOUSE_EVENT;
mouseRecords[i].Event.MouseEvent.dwEventFlags = MOUSE_MOVED;
events.push_back(IInputEvent::Create(mouseRecords[i]));
events.push_back(mouseRecords[i]);
}
// add an extra event
events.push_front(IInputEvent::Create(mouseRecords[0]));
inputBuffer.Flush();
// send one mouse event to possibly coalesce into later
VERIFY_IS_GREATER_THAN(inputBuffer.Write(std::move(events.front())), 0u);
events.pop_front();
VERIFY_IS_GREATER_THAN(inputBuffer.Write(mouseRecords[0]), 0u);
// write the others in bulk
VERIFY_IS_GREATER_THAN(inputBuffer.Write(events), 0u);
// no events should have been coalesced
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT + 1);
// check that the events stored match those inserted
VERIFY_ARE_EQUAL(inputBuffer._storage.front()->ToInputRecord(), mouseRecords[0]);
VERIFY_ARE_EQUAL(inputBuffer._storage.front(), mouseRecords[0]);
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_ARE_EQUAL(inputBuffer._storage[i + 1]->ToInputRecord(), mouseRecords[i]);
VERIFY_ARE_EQUAL(inputBuffer._storage[i + 1], mouseRecords[i]);
}
}
@ -180,7 +175,7 @@ class InputBufferTests
inputBuffer.Flush();
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
}
// all events should have been coalesced into one
@ -188,16 +183,12 @@ class InputBufferTests
// the single event should have a repeat count for each
// coalesced event
std::unique_ptr<IInputEvent> outEvent;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvent,
true,
false,
false,
false));
InputEventQueue outEvents;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents, 1, true, false, false, false));
VERIFY_ARE_NOT_EQUAL(nullptr, outEvent.get());
const auto pKeyEvent = static_cast<const KeyEvent* const>(outEvent.get());
VERIFY_ARE_EQUAL(pKeyEvent->GetRepeatCount(), RECORD_INSERT_COUNT);
VERIFY_IS_FALSE(outEvents.empty());
const auto& pKeyEvent = outEvents.front().Event.KeyEvent;
VERIFY_ARE_EQUAL(pKeyEvent.wRepeatCount, RECORD_INSERT_COUNT);
}
TEST_METHOD(InputBufferDoesNotCoalesceBulkKeyEvents)
@ -206,25 +197,25 @@ class InputBufferTests
InputBuffer inputBuffer;
INPUT_RECORD keyRecords[RECORD_INSERT_COUNT];
std::deque<std::unique_ptr<IInputEvent>> events;
InputEventQueue events;
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
keyRecords[i] = MakeKeyEvent(true, 1, L'a', 0, L'a', 0);
events.push_back(IInputEvent::Create(keyRecords[i]));
events.push_back(keyRecords[i]);
}
inputBuffer.Flush();
// send one key event to possibly coalesce into later
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(keyRecords[0])), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(keyRecords[0]), 0u);
// write the others in bulk
VERIFY_IS_GREATER_THAN(inputBuffer.Write(events), 0u);
// no events should have been coalesced
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT + 1);
// check that the events stored match those inserted
VERIFY_ARE_EQUAL(inputBuffer._storage.front()->ToInputRecord(), keyRecords[0]);
VERIFY_ARE_EQUAL(inputBuffer._storage.front(), keyRecords[0]);
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_ARE_EQUAL(inputBuffer._storage[i + 1]->ToInputRecord(), keyRecords[i]);
VERIFY_ARE_EQUAL(inputBuffer._storage[i + 1], keyRecords[i]);
}
}
@ -238,8 +229,8 @@ class InputBufferTests
inputBuffer.Flush();
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
VERIFY_ARE_EQUAL(inputBuffer._storage.back()->ToInputRecord(), record);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
VERIFY_ARE_EQUAL(inputBuffer._storage.back(), record);
}
// The events shouldn't be coalesced
@ -249,14 +240,14 @@ class InputBufferTests
TEST_METHOD(CanFlushAllOutput)
{
InputBuffer inputBuffer;
std::deque<std::unique_ptr<IInputEvent>> events;
InputEventQueue events;
// put some events in the buffer so we can remove them
INPUT_RECORD record;
record.EventType = MENU_EVENT;
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
events.push_back(IInputEvent::Create(record));
events.push_back(record);
}
VERIFY_IS_GREATER_THAN(inputBuffer.Write(events), 0u);
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
@ -270,13 +261,13 @@ class InputBufferTests
{
InputBuffer inputBuffer;
INPUT_RECORD records[RECORD_INSERT_COUNT] = { 0 };
std::deque<std::unique_ptr<IInputEvent>> inEvents;
InputEventQueue inEvents;
// create alternating mouse and key events
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
records[i].EventType = (i % 2 == 0) ? MENU_EVENT : KEY_EVENT;
inEvents.push_back(IInputEvent::Create(records[i]));
inEvents.push_back(records[i]);
}
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
@ -286,7 +277,7 @@ class InputBufferTests
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT / 2);
// make sure that the non key events were the ones removed
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
auto amountToRead = RECORD_INSERT_COUNT / 2;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
amountToRead,
@ -298,7 +289,7 @@ class InputBufferTests
for (size_t i = 0; i < outEvents.size(); ++i)
{
VERIFY_ARE_EQUAL(outEvents[i]->EventType(), InputEventType::KeyEvent);
VERIFY_ARE_EQUAL(outEvents[i].EventType, KEY_EVENT);
}
}
@ -306,18 +297,18 @@ class InputBufferTests
{
InputBuffer inputBuffer;
INPUT_RECORD records[RECORD_INSERT_COUNT];
std::deque<std::unique_ptr<IInputEvent>> inEvents;
InputEventQueue inEvents;
// write some input records
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(IInputEvent::Create(records[i]));
inEvents.push_back(records[i]);
}
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
// read them back out
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
auto amountToRead = RECORD_INSERT_COUNT;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
amountToRead,
@ -329,7 +320,7 @@ class InputBufferTests
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_ARE_EQUAL(records[i], outEvents[i]->ToInputRecord());
VERIFY_ARE_EQUAL(records[i], outEvents[i]);
}
}
@ -339,16 +330,16 @@ class InputBufferTests
// add some events so that we have something to peek at
INPUT_RECORD records[RECORD_INSERT_COUNT];
std::deque<std::unique_ptr<IInputEvent>> inEvents;
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(IInputEvent::Create(records[i]));
inEvents.push_back(records[i]);
}
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
// peek at events
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
auto amountToRead = RECORD_INSERT_COUNT;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
amountToRead,
@ -361,7 +352,7 @@ class InputBufferTests
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_ARE_EQUAL(records[i], outEvents[i]->ToInputRecord());
VERIFY_ARE_EQUAL(records[i], outEvents[i]);
}
}
@ -373,11 +364,11 @@ class InputBufferTests
// add some events so that we have something to stick in front of
INPUT_RECORD records[RECORD_INSERT_COUNT];
std::deque<std::unique_ptr<IInputEvent>> inEvents;
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(IInputEvent::Create(records[i]));
inEvents.push_back(records[i]);
}
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
@ -385,7 +376,7 @@ class InputBufferTests
waitEvent.SetEvent();
// read one record, hInputEvent should still be signaled
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
1,
false,
@ -435,17 +426,11 @@ class InputBufferTests
outRecordsExpected[3] = MakeKeyEvent(TRUE, 1, 0x3042, 0, 0xa0, 0);
outRecordsExpected[4].EventType = MOUSE_EVENT;
std::deque<std::unique_ptr<IInputEvent>> inEvents;
for (size_t i = 0; i < inRecords.size(); ++i)
{
inEvents.push_back(IInputEvent::Create(inRecords[i]));
}
inputBuffer.Flush();
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inRecords), 0u);
// read them out non-unicode style and compare
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
outRecordsExpected.size(),
false,
@ -455,7 +440,7 @@ class InputBufferTests
VERIFY_ARE_EQUAL(outEvents.size(), outRecordsExpected.size());
for (size_t i = 0; i < outEvents.size(); ++i)
{
VERIFY_ARE_EQUAL(outEvents[i]->ToInputRecord(), outRecordsExpected[i]);
VERIFY_ARE_EQUAL(outEvents[i], outRecordsExpected[i]);
}
}
@ -465,11 +450,11 @@ class InputBufferTests
// add some events so that we have something to stick in front of
INPUT_RECORD records[RECORD_INSERT_COUNT];
std::deque<std::unique_ptr<IInputEvent>> inEvents;
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(IInputEvent::Create(records[i]));
inEvents.push_back(records[i]);
}
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
@ -479,13 +464,13 @@ class InputBufferTests
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(IInputEvent::Create(prependRecords[i]));
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
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
auto amountToRead = RECORD_INSERT_COUNT;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
amountToRead,
@ -497,7 +482,7 @@ class InputBufferTests
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]->ToInputRecord());
VERIFY_ARE_EQUAL(prependRecords[i], outEvents[i]);
}
outEvents.clear();
@ -512,7 +497,7 @@ class InputBufferTests
VERIFY_ARE_EQUAL(amountToRead, outEvents.size());
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
{
VERIFY_ARE_EQUAL(records[i], outEvents[i]->ToInputRecord());
VERIFY_ARE_EQUAL(records[i], outEvents[i]);
}
}
@ -524,7 +509,7 @@ class InputBufferTests
// change the buffer's state a bit
INPUT_RECORD record;
record.EventType = MENU_EVENT;
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 1u);
inputBuffer.InputMode = 0x0;
inputBuffer.ReinitializeInputBuffer();
@ -544,7 +529,7 @@ class InputBufferTests
VERIFY_IS_FALSE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(pauseRecord)), 0u);
VERIFY_ARE_EQUAL(inputBuffer.Write(pauseRecord), 0u);
// we should now be paused and the input record should be discarded
VERIFY_IS_TRUE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
@ -552,7 +537,7 @@ class InputBufferTests
// the next key press should unpause us but be discarded
auto unpauseRecord = MakeKeyEvent(true, 1, L'a', 0, L'a', 0);
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(unpauseRecord)), 0u);
VERIFY_ARE_EQUAL(inputBuffer.Write(unpauseRecord), 0u);
VERIFY_IS_FALSE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
@ -569,7 +554,7 @@ class InputBufferTests
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
// pause the screen
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(pauseRecord)), 0u);
VERIFY_ARE_EQUAL(inputBuffer.Write(pauseRecord), 0u);
// we should now be paused and the input record should be discarded
VERIFY_IS_TRUE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
@ -578,12 +563,12 @@ class InputBufferTests
// sending a system key event should not stop the pause and
// the record should be stored in the input buffer
auto systemRecord = MakeKeyEvent(true, 1, VK_CONTROL, 0, 0, 0);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(systemRecord)), 0u);
VERIFY_IS_GREATER_THAN(inputBuffer.Write(systemRecord), 0u);
VERIFY_IS_TRUE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 1u);
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
size_t amountToRead = 2;
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
amountToRead,
@ -597,18 +582,18 @@ class InputBufferTests
{
InputBuffer inputBuffer;
auto record = MakeKeyEvent(true, 1, L'a', 0, L'a', 0);
auto inputEvent = IInputEvent::Create(record);
auto inputEvent = record;
size_t eventsWritten;
auto waitEvent = false;
inputBuffer.Flush();
// write one event to an empty buffer
std::deque<std::unique_ptr<IInputEvent>> storage;
InputEventQueue storage;
storage.push_back(std::move(inputEvent));
inputBuffer._WriteBuffer(storage, eventsWritten, waitEvent);
VERIFY_IS_TRUE(waitEvent);
// write another, it shouldn't signal this time
auto record2 = MakeKeyEvent(true, 1, L'b', 0, L'b', 0);
auto inputEvent2 = IInputEvent::Create(record2);
auto inputEvent2 = record2;
// write another event to a non-empty buffer
waitEvent = false;
storage.clear();
@ -623,9 +608,9 @@ class InputBufferTests
InputBuffer inputBuffer;
const WORD repeatCount = 5;
auto record = MakeKeyEvent(true, repeatCount, L'a', 0, L'a', 0);
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(record)), 1u);
VERIFY_ARE_EQUAL(inputBuffer.Write(record), 1u);
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
1,
false,
@ -634,8 +619,8 @@ class InputBufferTests
true));
VERIFY_ARE_EQUAL(outEvents.size(), 1u);
VERIFY_ARE_EQUAL(inputBuffer._storage.size(), 1u);
VERIFY_ARE_EQUAL(static_cast<const KeyEvent&>(*inputBuffer._storage.front()).GetRepeatCount(), repeatCount - 1);
VERIFY_ARE_EQUAL(static_cast<const KeyEvent&>(*outEvents.front()).GetRepeatCount(), 1u);
VERIFY_ARE_EQUAL(inputBuffer._storage.front().Event.KeyEvent.wRepeatCount, repeatCount - 1);
VERIFY_ARE_EQUAL(outEvents.front().Event.KeyEvent.wRepeatCount, 1u);
}
TEST_METHOD(StreamPeekingDeCoalesces)
@ -643,9 +628,9 @@ class InputBufferTests
InputBuffer inputBuffer;
const WORD repeatCount = 5;
auto record = MakeKeyEvent(true, repeatCount, L'a', 0, L'a', 0);
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(record)), 1u);
VERIFY_ARE_EQUAL(inputBuffer.Write(record), 1u);
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
1,
true,
@ -654,7 +639,7 @@ class InputBufferTests
true));
VERIFY_ARE_EQUAL(outEvents.size(), 1u);
VERIFY_ARE_EQUAL(inputBuffer._storage.size(), 1u);
VERIFY_ARE_EQUAL(static_cast<const KeyEvent&>(*inputBuffer._storage.front()).GetRepeatCount(), repeatCount);
VERIFY_ARE_EQUAL(static_cast<const KeyEvent&>(*outEvents.front()).GetRepeatCount(), 1u);
VERIFY_ARE_EQUAL(inputBuffer._storage.front().Event.KeyEvent.wRepeatCount, repeatCount);
VERIFY_ARE_EQUAL(outEvents.front().Event.KeyEvent.wRepeatCount, 1u);
}
};

View File

@ -278,6 +278,12 @@ namespace til
return tmp;
}
[[nodiscard]] friend constexpr small_vector_iterator operator+(const difference_type off, small_vector_iterator next) noexcept
{
next += off;
return next;
}
constexpr small_vector_iterator& operator-=(const difference_type off) noexcept
{
base::operator-=(off);

View File

@ -3,10 +3,6 @@
#include "precomp.h"
#include "../inc/EventSynthesis.hpp"
#include "../../types/inc/convert.hpp"
#include "../inc/VtApiRedirection.hpp"
#pragma hdrstop
// TODO: MSFT 14150722 - can these const values be generated at
// runtime without breaking compatibility?
@ -16,35 +12,29 @@ static constexpr WORD leftShiftScanCode = 0x2A;
// Routine Description:
// - naively determines the width of a UCS2 encoded wchar (with caveats noted above)
#pragma warning(suppress : 4505) // this function will be deleted if numpad events are disabled
static CodepointWidth GetQuickCharWidthLegacyForNumpadEventSynthesis(const wchar_t wch) noexcept
static bool IsCharFullWidth(const wchar_t wch) noexcept
{
if ((0x1100 <= wch && wch <= 0x115f) // From Unicode 9.0, Hangul Choseong is wide
|| (0x2e80 <= wch && wch <= 0x303e) // From Unicode 9.0, this range is wide (assorted languages)
|| (0x3041 <= wch && wch <= 0x3094) // Hiragana
|| (0x30a1 <= wch && wch <= 0x30f6) // Katakana
|| (0x3105 <= wch && wch <= 0x312c) // Bopomofo
|| (0x3131 <= wch && wch <= 0x318e) // Hangul Elements
|| (0x3190 <= wch && wch <= 0x3247) // From Unicode 9.0, this range is wide
|| (0x3251 <= wch && wch <= 0x4dbf) // Unicode 9.0 CJK Unified Ideographs, Yi, Reserved, Han Ideograph (hexagrams from 4DC0..4DFF are ignored
|| (0x4e00 <= wch && wch <= 0xa4c6) // Unicode 9.0 CJK Unified Ideographs, Yi, Reserved, Han Ideograph (hexagrams from 4DC0..4DFF are ignored
|| (0xa960 <= wch && wch <= 0xa97c) // Wide Hangul Choseong
|| (0xac00 <= wch && wch <= 0xd7a3) // Korean Hangul Syllables
|| (0xf900 <= wch && wch <= 0xfaff) // From Unicode 9.0, this range is wide [CJK Compatibility Ideographs, Includes Han Compatibility Ideographs]
|| (0xfe10 <= wch && wch <= 0xfe1f) // From Unicode 9.0, this range is wide [Presentation forms]
|| (0xfe30 <= wch && wch <= 0xfe6b) // From Unicode 9.0, this range is wide [Presentation forms]
|| (0xff01 <= wch && wch <= 0xff5e) // Fullwidth ASCII variants
|| (0xffe0 <= wch && wch <= 0xffe6)) // Fullwidth symbol variants
{
return CodepointWidth::Wide;
}
return CodepointWidth::Narrow;
return (0x1100 <= wch && wch <= 0x115f) || // From Unicode 9.0, Hangul Choseong is wide
(0x2e80 <= wch && wch <= 0x303e) || // From Unicode 9.0, this range is wide (assorted languages)
(0x3041 <= wch && wch <= 0x3094) || // Hiragana
(0x30a1 <= wch && wch <= 0x30f6) || // Katakana
(0x3105 <= wch && wch <= 0x312c) || // Bopomofo
(0x3131 <= wch && wch <= 0x318e) || // Hangul Elements
(0x3190 <= wch && wch <= 0x3247) || // From Unicode 9.0, this range is wide
(0x3251 <= wch && wch <= 0x4dbf) || // Unicode 9.0 CJK Unified Ideographs, Yi, Reserved, Han Ideograph (hexagrams from 4DC0..4DFF are ignored
(0x4e00 <= wch && wch <= 0xa4c6) || // Unicode 9.0 CJK Unified Ideographs, Yi, Reserved, Han Ideograph (hexagrams from 4DC0..4DFF are ignored
(0xa960 <= wch && wch <= 0xa97c) || // Wide Hangul Choseong
(0xac00 <= wch && wch <= 0xd7a3) || // Korean Hangul Syllables
(0xf900 <= wch && wch <= 0xfaff) || // From Unicode 9.0, this range is wide [CJK Compatibility Ideographs, Includes Han Compatibility Ideographs]
(0xfe10 <= wch && wch <= 0xfe1f) || // From Unicode 9.0, this range is wide [Presentation forms]
(0xfe30 <= wch && wch <= 0xfe6b) || // From Unicode 9.0, this range is wide [Presentation forms]
(0xff01 <= wch && wch <= 0xff5e) || // Fullwidth ASCII variants
(0xffe0 <= wch && wch <= 0xffe6); // Fullwidth symbol variants
}
std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::CharToKeyEvents(const wchar_t wch,
const unsigned int codepage)
void Microsoft::Console::Interactivity::CharToKeyEvents(const wchar_t wch, const unsigned int codepage, InputEventQueue& keyEvents)
{
const short invalidKey = -1;
static constexpr short invalidKey = -1;
auto keyState = OneCoreSafeVkKeyScanW(wch);
if (keyState == invalidKey)
@ -57,18 +47,19 @@ std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::CharToK
WORD CharType = 0;
GetStringTypeW(CT_CTYPE3, &wch, 1, &CharType);
if (!(WI_IsFlagSet(CharType, C3_ALPHA) || GetQuickCharWidthLegacyForNumpadEventSynthesis(wch) == CodepointWidth::Wide))
if (WI_IsFlagClear(CharType, C3_ALPHA) && !IsCharFullWidth(wch))
{
// It wasn't alphanumeric or determined to be wide by the old algorithm
// if VkKeyScanW fails (char is not in kbd layout), we must
// emulate the key being input through the numpad
return SynthesizeNumpadEvents(wch, codepage);
SynthesizeNumpadEvents(wch, codepage, keyEvents);
return;
}
}
keyState = 0; // SynthesizeKeyboardEvents would rather get 0 than -1
}
return SynthesizeKeyboardEvents(wch, keyState);
SynthesizeKeyboardEvents(wch, keyState, keyEvents);
}
// Routine Description:
@ -80,80 +71,45 @@ std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::CharToK
// - deque of KeyEvents that represent the wchar_t being typed
// Note:
// - will throw exception on error
std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::SynthesizeKeyboardEvents(const wchar_t wch, const short keyState)
void Microsoft::Console::Interactivity::SynthesizeKeyboardEvents(const wchar_t wch, const short keyState, InputEventQueue& keyEvents)
{
const auto modifierState = HIBYTE(keyState);
auto altGrSet = false;
auto shiftSet = false;
std::deque<std::unique_ptr<KeyEvent>> keyEvents;
// add modifier key event if necessary
if (WI_AreAllFlagsSet(modifierState, VkKeyScanModState::CtrlAndAltPressed))
{
altGrSet = true;
keyEvents.push_back(std::make_unique<KeyEvent>(true,
1ui16,
static_cast<WORD>(VK_MENU),
altScanCode,
UNICODE_NULL,
(ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)));
}
else if (WI_IsFlagSet(modifierState, VkKeyScanModState::ShiftPressed))
{
shiftSet = true;
keyEvents.push_back(std::make_unique<KeyEvent>(true,
1ui16,
static_cast<WORD>(VK_SHIFT),
leftShiftScanCode,
UNICODE_NULL,
SHIFT_PRESSED));
}
const auto vk = LOBYTE(keyState);
const auto virtualScanCode = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
KeyEvent keyEvent{ true, 1, LOBYTE(keyState), virtualScanCode, wch, 0 };
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);
// add modifier flags if necessary
if (WI_IsFlagSet(modifierState, VkKeyScanModState::ShiftPressed))
{
keyEvent.ActivateModifierKey(ModifierKeyState::Shift);
}
if (WI_IsFlagSet(modifierState, VkKeyScanModState::CtrlPressed))
{
keyEvent.ActivateModifierKey(ModifierKeyState::LeftCtrl);
}
if (WI_AreAllFlagsSet(modifierState, VkKeyScanModState::CtrlAndAltPressed))
{
keyEvent.ActivateModifierKey(ModifierKeyState::RightAlt);
}
// add key event down and up
keyEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
keyEvent.SetKeyDown(false);
keyEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
// add modifier key up event
if (altGrSet)
{
keyEvents.push_back(std::make_unique<KeyEvent>(false,
1ui16,
static_cast<WORD>(VK_MENU),
altScanCode,
UNICODE_NULL,
ENHANCED_KEY));
keyEvents.push_back(SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, 0, ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED));
}
else if (shiftSet)
{
keyEvents.push_back(std::make_unique<KeyEvent>(false,
1ui16,
static_cast<WORD>(VK_SHIFT),
leftShiftScanCode,
UNICODE_NULL,
0));
keyEvents.push_back(SynthesizeKeyEvent(true, 1, VK_SHIFT, leftShiftScanCode, 0, SHIFT_PRESSED));
}
return keyEvents;
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);
keyEvents.push_back(keyEvent);
keyEvent.Event.KeyEvent.bKeyDown = FALSE;
keyEvents.push_back(keyEvent);
// handle yucky alt-gr keys
if (altGrSet)
{
keyEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, 0, ENHANCED_KEY));
}
else if (shiftSet)
{
keyEvents.push_back(SynthesizeKeyEvent(false, 1, VK_SHIFT, leftShiftScanCode, 0, 0));
}
}
// Routine Description:
@ -166,64 +122,35 @@ std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::Synthes
// alt + numpad
// Note:
// - will throw exception on error
std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage)
void Microsoft::Console::Interactivity::SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage, InputEventQueue& keyEvents)
{
std::deque<std::unique_ptr<KeyEvent>> keyEvents;
char converted = 0;
const auto result = WideCharToMultiByte(codepage, 0, &wch, 1, &converted, 1, nullptr, nullptr);
//alt keydown
keyEvents.push_back(std::make_unique<KeyEvent>(true,
1ui16,
static_cast<WORD>(VK_MENU),
altScanCode,
UNICODE_NULL,
LEFT_ALT_PRESSED));
std::wstring wstr{ wch };
const auto convertedChars = ConvertToA(codepage, wstr);
if (convertedChars.size() == 1)
if (result == 1)
{
// alt keydown
keyEvents.push_back(SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, 0, LEFT_ALT_PRESSED));
// It is OK if the char is "signed -1", we want to interpret that as "unsigned 255" for the
// "integer to character" conversion below with ::to_string, thus the static_cast.
// Prime example is nonbreaking space U+00A0 will convert to OEM by codepage 437 to 0xFF which is -1 signed.
// But it is absolutely valid as 0xFF or 255 unsigned as the correct CP437 character.
// We need to treat it as unsigned because we're going to pretend it was a keypad entry
// and you don't enter negative numbers on the keypad.
const auto uch = static_cast<unsigned char>(convertedChars.at(0));
const auto charString = std::to_string(static_cast<unsigned char>(converted));
// unsigned char values are in the range [0, 255] so we need to be
// able to store up to 4 chars from the conversion (including the end of string char)
auto charString = std::to_string(uch);
for (auto& ch : std::string_view(charString))
for (const auto& ch : charString)
{
if (ch == 0)
{
break;
}
const WORD virtualKey = ch - '0' + VK_NUMPAD0;
const auto virtualScanCode = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(virtualKey, MAPVK_VK_TO_VSC));
keyEvents.push_back(std::make_unique<KeyEvent>(true,
1ui16,
virtualKey,
virtualScanCode,
UNICODE_NULL,
LEFT_ALT_PRESSED));
keyEvents.push_back(std::make_unique<KeyEvent>(false,
1ui16,
virtualKey,
virtualScanCode,
UNICODE_NULL,
LEFT_ALT_PRESSED));
const WORD vk = ch - '0' + VK_NUMPAD0;
const auto sc = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
auto keyEvent = SynthesizeKeyEvent(true, 1, vk, sc, 0, LEFT_ALT_PRESSED);
keyEvents.push_back(keyEvent);
keyEvent.Event.KeyEvent.bKeyDown = FALSE;
keyEvents.push_back(keyEvent);
}
}
// alt keyup
keyEvents.push_back(std::make_unique<KeyEvent>(false,
1ui16,
static_cast<WORD>(VK_MENU),
altScanCode,
wch,
0));
return keyEvents;
// alt keyup
keyEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, wch, 0));
}
}

View File

@ -14,16 +14,10 @@ Author:
--*/
#pragma once
#include <deque>
#include <memory>
#include "../../types/inc/IInputEvent.hpp"
namespace Microsoft::Console::Interactivity
{
std::deque<std::unique_ptr<KeyEvent>> CharToKeyEvents(const wchar_t wch, const unsigned int codepage);
std::deque<std::unique_ptr<KeyEvent>> SynthesizeKeyboardEvents(const wchar_t wch,
const short keyState);
std::deque<std::unique_ptr<KeyEvent>> SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage);
void CharToKeyEvents(wchar_t wch, unsigned int codepage, InputEventQueue& out);
void SynthesizeKeyboardEvents(wchar_t wch, short keyState, InputEventQueue& out);
void SynthesizeNumpadEvents(wchar_t wch, unsigned int codepage, InputEventQueue& out);
}

View File

@ -266,9 +266,7 @@ VOID ConIoSrvComm::ServiceInputPipe()
case CIS_EVENT_TYPE_INPUT:
try
{
const auto keyRecord = Event.InputEvent.Record.Event.KeyEvent;
const KeyEvent keyEvent{ keyRecord };
HandleGenericKeyEvent(keyEvent, false);
HandleGenericKeyEvent(Event.InputEvent.Record, false);
}
catch (...)
{

View File

@ -134,17 +134,17 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData,
// - deque of KeyEvents that represent the string passed in
// Note:
// - will throw exception on error
std::deque<std::unique_ptr<IInputEvent>> Clipboard::TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
const size_t cchData,
const bool bracketedPaste)
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);
std::deque<std::unique_ptr<IInputEvent>> keyEvents;
InputEventQueue keyEvents;
const auto pushControlSequence = [&](const std::wstring_view sequence) {
std::for_each(sequence.begin(), sequence.end(), [&](const auto wch) {
keyEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
keyEvents.push_back(std::make_unique<KeyEvent>(false, 1ui16, 0ui16, 0ui16, wch, 0));
keyEvents.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
keyEvents.push_back(SynthesizeKeyEvent(false, 1, 0, 0, wch, 0));
});
};
@ -194,18 +194,14 @@ std::deque<std::unique_ptr<IInputEvent>> Clipboard::TextToKeyEvents(_In_reads_(c
}
const auto codepage = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
auto convertedEvents = CharToKeyEvents(currentChar, codepage);
while (!convertedEvents.empty())
{
keyEvents.push_back(std::move(convertedEvents.front()));
convertedEvents.pop_front();
}
CharToKeyEvents(currentChar, codepage, keyEvents);
}
if (bracketedPaste)
{
pushControlSequence(L"\x1b[201~");
}
return keyEvents;
}

View File

@ -35,9 +35,9 @@ namespace Microsoft::Console::Interactivity::Win32
void Paste();
private:
std::deque<std::unique_ptr<IInputEvent>> TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
const size_t cchData,
const bool bracketedPaste = false);
InputEventQueue TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
const size_t cchData,
const bool bracketedPaste = false);
void StoreSelectionToClipboard(_In_ const bool fAlsoCopyFormatting);

View File

@ -186,23 +186,19 @@ void HandleKeyEvent(const HWND hWnd,
}
}
KeyEvent keyEvent{ !!bKeyDown, RepeatCount, VirtualKeyCode, VirtualScanCode, UNICODE_NULL, 0 };
auto keyEvent = SynthesizeKeyEvent(bKeyDown, RepeatCount, VirtualKeyCode, VirtualScanCode, UNICODE_NULL, 0);
if (IsCharacterMessage)
{
// If this is a fake character, zero the scancode.
if (lParam & 0x02000000)
{
keyEvent.SetVirtualScanCode(0);
keyEvent.Event.KeyEvent.wVirtualScanCode = 0;
}
keyEvent.SetActiveModifierKeys(GetControlKeyState(lParam));
keyEvent.Event.KeyEvent.dwControlKeyState = GetControlKeyState(lParam);
if (Message == WM_CHAR || Message == WM_SYSCHAR)
{
keyEvent.SetCharData(static_cast<wchar_t>(wParam));
}
else
{
keyEvent.SetCharData(L'\0');
keyEvent.Event.KeyEvent.uChar.UnicodeChar = static_cast<wchar_t>(wParam);
}
}
else
@ -212,8 +208,7 @@ void HandleKeyEvent(const HWND hWnd,
{
return;
}
keyEvent.SetActiveModifierKeys(ControlKeyState);
keyEvent.SetCharData(L'\0');
keyEvent.Event.KeyEvent.dwControlKeyState = ControlKeyState;
}
const INPUT_KEY_INFO inputKeyInfo(VirtualKeyCode, ControlKeyState);
@ -922,26 +917,12 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
break;
}
ULONG EventsWritten = 0;
try
{
auto mouseEvent = std::make_unique<MouseEvent>(
MousePosition,
ConvertMouseButtonState(ButtonFlags, static_cast<UINT>(wParam)),
GetControlKeyState(0),
EventFlags);
EventsWritten = static_cast<ULONG>(gci.pInputBuffer->Write(std::move(mouseEvent)));
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
EventsWritten = 0;
}
if (EventsWritten != 1)
{
RIPMSG1(RIP_WARNING, "PutInputInBuffer: EventsWritten != 1 (0x%x), 1 expected", EventsWritten);
}
const auto mouseEvent = SynthesizeMouseEvent(
MousePosition,
ConvertMouseButtonState(ButtonFlags, static_cast<UINT>(wParam)),
GetControlKeyState(0),
EventFlags);
gci.pInputBuffer->Write(mouseEvent);
return FALSE;
}

View File

@ -212,19 +212,7 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m)
}
else
{
try
{
for (size_t i = 0; i < cRecords; ++i)
{
if (outEvents.empty())
{
break;
}
rgRecords[i] = outEvents.front()->ToInputRecord();
outEvents.pop_front();
}
}
CATCH_RETURN();
std::ranges::copy(outEvents, rgRecords);
}
if (SUCCEEDED(hr))

View File

@ -135,7 +135,7 @@ bool ConsoleWaitBlock::Notify(const WaitTerminationReason TerminationReason)
DWORD dwControlKeyState;
auto fIsUnicode = true;
std::deque<std::unique_ptr<IInputEvent>> outEvents;
InputEventQueue outEvents;
// TODO: MSFT 14104228 - get rid of this void* and get the data
// out of the read wait object properly.
void* pOutputData = nullptr;
@ -193,15 +193,7 @@ bool ConsoleWaitBlock::Notify(const WaitTerminationReason TerminationReason)
const auto pRecordBuffer = static_cast<INPUT_RECORD* const>(buffer);
a->NumRecords = static_cast<ULONG>(outEvents.size());
for (size_t i = 0; i < a->NumRecords; ++i)
{
if (outEvents.empty())
{
break;
}
pRecordBuffer[i] = outEvents.front()->ToInputRecord();
outEvents.pop_front();
}
std::ranges::copy(outEvents, pRecordBuffer);
}
else if (API_NUMBER_READCONSOLE == _WaitReplyMessage.msgHeader.ApiNumber)
{

View File

@ -28,9 +28,9 @@ namespace Microsoft::Console::VirtualTerminal
virtual ~IInteractDispatch() = default;
#pragma warning(pop)
virtual bool WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inputEvents) = 0;
virtual bool WriteInput(const std::span<const INPUT_RECORD>& inputEvents) = 0;
virtual bool WriteCtrlKey(const KeyEvent& event) = 0;
virtual bool WriteCtrlKey(const INPUT_RECORD& event) = 0;
virtual bool WriteString(const std::wstring_view string) = 0;

View File

@ -17,7 +17,6 @@
#include "../../interactivity/inc/ServiceLocator.hpp"
#include "../../interactivity/inc/EventSynthesis.hpp"
#include "../../types/inc/Viewport.hpp"
#include "../../inc/unicode.hpp"
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::Types;
@ -38,7 +37,7 @@ InteractDispatch::InteractDispatch() :
// - inputEvents: a collection of IInputEvents
// Return Value:
// - True.
bool InteractDispatch::WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inputEvents)
bool InteractDispatch::WriteInput(const std::span<const INPUT_RECORD>& inputEvents)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetActiveInputBuffer()->Write(inputEvents);
@ -52,17 +51,14 @@ bool InteractDispatch::WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inpu
// client application.
// Arguments:
// - event: The key to send to the host.
// Return Value:
// - True.
bool InteractDispatch::WriteCtrlKey(const KeyEvent& event)
bool InteractDispatch::WriteCtrlKey(const INPUT_RECORD& event)
{
HandleGenericKeyEvent(event, false);
return true;
}
// Method Description:
// - Writes a string of input to the host. The string is converted to keystrokes
// that will faithfully represent the input by CharToKeyEvents.
// - Writes a string of input to the host.
// Arguments:
// - string : a string to write to the console.
// Return Value:
@ -72,15 +68,11 @@ bool InteractDispatch::WriteString(const std::wstring_view string)
if (!string.empty())
{
const auto codepage = _api.GetConsoleOutputCP();
std::deque<std::unique_ptr<IInputEvent>> keyEvents;
InputEventQueue keyEvents;
for (const auto& wch : string)
{
auto convertedEvents = CharToKeyEvents(wch, codepage);
std::move(convertedEvents.begin(),
convertedEvents.end(),
std::back_inserter(keyEvents));
CharToKeyEvents(wch, codepage, keyEvents);
}
WriteInput(keyEvents);

View File

@ -25,8 +25,8 @@ namespace Microsoft::Console::VirtualTerminal
public:
InteractDispatch();
bool WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inputEvents) override;
bool WriteCtrlKey(const KeyEvent& event) override;
bool WriteInput(const std::span<const INPUT_RECORD>& inputEvents) override;
bool WriteCtrlKey(const INPUT_RECORD& event) override;
bool WriteString(const std::wstring_view string) override;
bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const VTParameter parameter1,

View File

@ -2,9 +2,8 @@
// Licensed under the MIT license.
#include "precomp.h"
#include <wextestclass.h>
#include "../../inc/consoletaeftemplates.hpp"
#include "../types/inc/IInputEvent.hpp"
#include "../terminal/input/terminalInput.hpp"
using namespace WEX::Common;

View File

@ -2,14 +2,10 @@
// Licensed under the MIT license.
#include "precomp.h"
#include "../precomp.h"
#include <windows.h>
#include <wextestclass.h>
#include "../../inc/consoletaeftemplates.hpp"
#include "../../input/terminalInput.hpp"
#include "../../../interactivity/inc/VtApiRedirection.hpp"
#include "../../input/terminalInput.hpp"
#include "../types/inc/IInputEvent.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
@ -182,9 +178,8 @@ void InputTest::TerminalInputTests()
break;
}
auto inputEvent = IInputEvent::Create(irTest);
// Send key into object (will trigger callback and verification)
VERIFY_ARE_EQUAL(expected, input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been.");
}
Log::Comment(L"Sending every possible VKEY at the input stream for interception during key UP.");
@ -198,9 +193,8 @@ void InputTest::TerminalInputTests()
irTest.Event.KeyEvent.wVirtualKeyCode = vkey;
irTest.Event.KeyEvent.bKeyDown = FALSE;
auto inputEvent = IInputEvent::Create(irTest);
// Send key into object (will trigger callback and verification)
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify key was NOT handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irTest), L"Verify key was NOT handled.");
}
Log::Comment(L"Verify other types of events are not handled/intercepted.");
@ -209,18 +203,15 @@ void InputTest::TerminalInputTests()
Log::Comment(L"Testing MOUSE_EVENT");
irUnhandled.EventType = MOUSE_EVENT;
auto inputEvent = IInputEvent::Create(irUnhandled);
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify MOUSE_EVENT was NOT handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irUnhandled), L"Verify MOUSE_EVENT was NOT handled.");
Log::Comment(L"Testing WINDOW_BUFFER_SIZE_EVENT");
irUnhandled.EventType = WINDOW_BUFFER_SIZE_EVENT;
inputEvent = IInputEvent::Create(irUnhandled);
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify WINDOW_BUFFER_SIZE_EVENT was NOT handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irUnhandled), L"Verify WINDOW_BUFFER_SIZE_EVENT was NOT handled.");
Log::Comment(L"Testing MENU_EVENT");
irUnhandled.EventType = MENU_EVENT;
inputEvent = IInputEvent::Create(irUnhandled);
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify MENU_EVENT was NOT handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irUnhandled), L"Verify MENU_EVENT was NOT handled.");
// Testing FOCUS_EVENTs is handled by TestFocusEvents
}
@ -232,40 +223,13 @@ void InputTest::TestFocusEvents()
// We're relying on the fact that the INPUT_RECORD version of the ctor is only called by the API
TerminalInput input;
INPUT_RECORD irTest = { 0 };
irTest.EventType = FOCUS_EVENT;
{
irTest.Event.FocusEvent.bSetFocus = false;
auto inputEvent = IInputEvent::Create(irTest);
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled.");
}
{
irTest.Event.FocusEvent.bSetFocus = true;
auto inputEvent = IInputEvent::Create(irTest);
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled.");
}
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(false), L"Verify FocusEvent from any other source was NOT handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(true), L"Verify FocusEvent from any other source was NOT handled.");
Log::Comment(L"Enable focus event handling");
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(false));
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(true));
input.SetInputMode(TerminalInput::Mode::FocusEvent, true);
{
irTest.Event.FocusEvent.bSetFocus = false;
auto inputEvent = IInputEvent::Create(irTest);
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled.");
}
{
irTest.Event.FocusEvent.bSetFocus = true;
auto inputEvent = IInputEvent::Create(irTest);
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled.");
}
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[O"), input.HandleFocus(false), L"Verify FocusEvent from any other source was handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[I"), input.HandleFocus(true), L"Verify FocusEvent from any other source was handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[O"), input.HandleFocus(false));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[I"), input.HandleFocus(true));
}
void InputTest::TerminalInputModifierKeyTests()
@ -482,9 +446,8 @@ void InputTest::TerminalInputModifierKeyTests()
str[str.size() - 2] = L'1' + (fShift ? 1 : 0) + (fAlt ? 2 : 0) + (fCtrl ? 4 : 0);
}
auto inputEvent = IInputEvent::Create(irTest);
// Send key into object (will trigger callback and verification)
VERIFY_ARE_EQUAL(expected, input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been.");
}
}
@ -509,27 +472,23 @@ void InputTest::TerminalInputNullKeyTests()
irTest.Event.KeyEvent.bKeyDown = TRUE;
// Send key into object (will trigger callback and verification)
auto inputEvent = IInputEvent::Create(irTest);
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
vkey = VK_SPACE;
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
irTest.Event.KeyEvent.wVirtualKeyCode = vkey;
irTest.Event.KeyEvent.uChar.UnicodeChar = vkey;
inputEvent = IInputEvent::Create(irTest);
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
irTest.Event.KeyEvent.dwControlKeyState = uiKeystate;
inputEvent = IInputEvent::Create(irTest);
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
uiKeystate = RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED;
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
irTest.Event.KeyEvent.dwControlKeyState = uiKeystate;
inputEvent = IInputEvent::Create(irTest);
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
}
static void TestKey(const TerminalInput::OutputType& expected, TerminalInput& input, const unsigned int uiKeystate, const BYTE vkey, const wchar_t wch = 0)
@ -545,8 +504,7 @@ static void TestKey(const TerminalInput::OutputType& expected, TerminalInput& in
irTest.Event.KeyEvent.uChar.UnicodeChar = wch;
// Send key into object (will trigger callback and verification)
auto inputEvent = IInputEvent::Create(irTest);
VERIFY_ARE_EQUAL(expected, input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been.");
}
void InputTest::DifferentModifiersTest()
@ -706,23 +664,23 @@ void InputTest::BackarrowKeyModeTest()
void InputTest::AutoRepeatModeTest()
{
const auto down = std::make_unique<KeyEvent>(true, 1ui16, 'A', 0ui16, 'A', 0ui16);
const auto up = std::make_unique<KeyEvent>(false, 1ui16, 'A', 0ui16, 'A', 0ui16);
static constexpr auto down = SynthesizeKeyEvent(true, 1, 'A', 0, 'A', 0);
static constexpr auto up = SynthesizeKeyEvent(false, 1, 'A', 0, 'A', 0);
TerminalInput input;
Log::Comment(L"Sending repeating keypresses with DECARM disabled.");
input.SetInputMode(TerminalInput::Mode::AutoRepeat, false);
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get()));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down.get()));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down.get()));
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up.get()));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up));
Log::Comment(L"Sending repeating keypresses with DECARM enabled.");
input.SetInputMode(TerminalInput::Mode::AutoRepeat, true);
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get()));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get()));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get()));
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up.get()));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up));
}

View File

@ -4,6 +4,7 @@
#include "precomp.h"
#include "terminalInput.hpp"
#include "../types/inc/IInputEvent.hpp"
#include "../types/inc/utils.hpp"
using namespace Microsoft::Console::VirtualTerminal;

View File

@ -6,8 +6,9 @@
#include <til/unicode.h>
#include "../../interactivity/inc/VtApiRedirection.hpp"
#include "../../inc/unicode.hpp"
#include "../../interactivity/inc/VtApiRedirection.hpp"
#include "../types/inc/IInputEvent.hpp"
using namespace Microsoft::Console::VirtualTerminal;
@ -268,45 +269,45 @@ void TerminalInput::ForceDisableWin32InputMode(const bool win32InputMode) noexce
_forceDisableWin32InputMode = win32InputMode;
}
static const std::span<const TermKeyMap> _getKeyMapping(const KeyEvent& keyEvent,
const bool ansiMode,
const bool cursorApplicationMode,
const bool keypadApplicationMode) noexcept
static std::span<const TermKeyMap> _getKeyMapping(const KEY_EVENT_RECORD& keyEvent, const bool ansiMode, const bool cursorApplicationMode, const bool keypadApplicationMode) noexcept
{
// Cursor keys: VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN
const auto isCursorKey = keyEvent.wVirtualKeyCode >= VK_END && keyEvent.wVirtualKeyCode <= VK_DOWN;
if (ansiMode)
{
if (keyEvent.IsCursorKey())
if (isCursorKey)
{
if (cursorApplicationMode)
{
return { s_cursorKeysApplicationMapping.data(), s_cursorKeysApplicationMapping.size() };
return s_cursorKeysApplicationMapping;
}
else
{
return { s_cursorKeysNormalMapping.data(), s_cursorKeysNormalMapping.size() };
return s_cursorKeysNormalMapping;
}
}
else
{
if (keypadApplicationMode)
{
return { s_keypadApplicationMapping.data(), s_keypadApplicationMapping.size() };
return s_keypadApplicationMapping;
}
else
{
return { s_keypadNumericMapping.data(), s_keypadNumericMapping.size() };
return s_keypadNumericMapping;
}
}
}
else
{
if (keyEvent.IsCursorKey())
if (isCursorKey)
{
return { s_cursorKeysVt52Mapping.data(), s_cursorKeysVt52Mapping.size() };
return s_cursorKeysVt52Mapping;
}
else
{
return { s_keypadVt52Mapping.data(), s_keypadVt52Mapping.size() };
return s_keypadVt52Mapping;
}
}
}
@ -318,12 +319,12 @@ static const std::span<const TermKeyMap> _getKeyMapping(const KeyEvent& keyEvent
// - keyMapping - Array of key mappings to search
// Return Value:
// - Has value if there was a match to a key translation.
static std::optional<const TermKeyMap> _searchKeyMapping(const KeyEvent& keyEvent,
static std::optional<const TermKeyMap> _searchKeyMapping(const KEY_EVENT_RECORD& keyEvent,
std::span<const TermKeyMap> keyMapping) noexcept
{
for (auto& map : keyMapping)
{
if (map.vkey == keyEvent.GetVirtualKeyCode())
if (map.vkey == keyEvent.wVirtualKeyCode)
{
// If the mapping has no modifiers set, then it doesn't really care
// what the modifiers are on the key. The caller will likely do
@ -337,9 +338,9 @@ static std::optional<const TermKeyMap> _searchKeyMapping(const KeyEvent& keyEven
// The modifier mapping expects certain modifier keys to be
// pressed. Check those as well.
modifiersMatch =
(WI_IsFlagSet(map.modifiers, SHIFT_PRESSED) == keyEvent.IsShiftPressed()) &&
(WI_IsAnyFlagSet(map.modifiers, ALT_PRESSED) == keyEvent.IsAltPressed()) &&
(WI_IsAnyFlagSet(map.modifiers, CTRL_PRESSED) == keyEvent.IsCtrlPressed());
WI_IsAnyFlagSet(map.modifiers, SHIFT_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED) &&
WI_IsAnyFlagSet(map.modifiers, ALT_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) &&
WI_IsAnyFlagSet(map.modifiers, CTRL_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
}
if (modifiersMatch)
@ -353,16 +354,16 @@ static std::optional<const TermKeyMap> _searchKeyMapping(const KeyEvent& keyEven
// Searches the s_modifierKeyMapping for a entry corresponding to this key event.
// Changes the second to last byte to correspond to the currently pressed modifier keys.
TerminalInput::OutputType TerminalInput::_searchWithModifier(const KeyEvent& keyEvent)
TerminalInput::OutputType TerminalInput::_searchWithModifier(const KEY_EVENT_RECORD& keyEvent)
{
if (const auto match = _searchKeyMapping(keyEvent, s_modifierKeyMapping))
{
const auto& v = match.value();
if (!v.sequence.empty())
{
const auto shift = keyEvent.IsShiftPressed();
const auto alt = keyEvent.IsAltPressed();
const auto ctrl = keyEvent.IsCtrlPressed();
const auto shift = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED);
const auto alt = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED);
const auto ctrl = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
StringType str{ v.sequence };
str.at(str.size() - 2) = L'1' + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0);
return str;
@ -407,12 +408,12 @@ TerminalInput::OutputType TerminalInput::_searchWithModifier(const KeyEvent& key
const auto slashVkey = LOBYTE(slashKeyScan);
const auto questionMarkVkey = LOBYTE(questionMarkKeyScan);
const auto ctrl = keyEvent.IsCtrlPressed();
const auto alt = keyEvent.IsAltPressed();
const auto shift = keyEvent.IsShiftPressed();
const auto ctrl = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
const auto alt = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED);
const auto shift = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED);
// From the KeyEvent we're translating, synthesize the equivalent VkKeyScan result
const auto vkey = keyEvent.GetVirtualKeyCode();
const auto vkey = keyEvent.wVirtualKeyCode;
const short keyScanFromEvent = vkey |
(shift ? 0x100 : 0) |
(ctrl ? 0x200 : 0) |
@ -474,20 +475,15 @@ TerminalInput::OutputType TerminalInput::MakeOutput(const std::wstring_view& str
// Return Value:
// - Returns an empty optional if we didn't handle the key event and the caller can opt to handle it in some other way.
// - Returns a string if we successfully translated it into a VT input sequence.
TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInEvent)
TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
{
if (!pInEvent)
{
return MakeUnhandled();
}
// On key presses, prepare to translate to VT compatible sequences
if (pInEvent->EventType() != InputEventType::KeyEvent)
if (event.EventType != KEY_EVENT)
{
return MakeUnhandled();
}
auto keyEvent = *static_cast<const KeyEvent* const>(pInEvent);
auto keyEvent = event.Event.KeyEvent;
// GH#4999 - If we're in win32-input mode, skip straight to doing that.
// Since this mode handles all types of key events, do nothing else.
@ -498,10 +494,10 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
}
// Check if this key matches the last recorded key code.
const auto matchingLastKeyPress = _lastVirtualKeyCode == keyEvent.GetVirtualKeyCode();
const auto matchingLastKeyPress = _lastVirtualKeyCode == keyEvent.wVirtualKeyCode;
// Only need to handle key down. See raw key handler (see RawReadWaitRoutine in stream.cpp)
if (!keyEvent.IsKeyDown())
if (!keyEvent.bKeyDown)
{
// If this is a release of the last recorded key press, we can reset that.
if (matchingLastKeyPress)
@ -519,17 +515,17 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
// the event, otherwise the key press can still end up being submitted.
return MakeOutput({});
}
_lastVirtualKeyCode = keyEvent.GetVirtualKeyCode();
_lastVirtualKeyCode = keyEvent.wVirtualKeyCode;
// The VK_BACK key depends on the state of Backarrow Key mode (DECBKM).
// If the mode is set, we should send BS. If reset, we should send DEL.
if (keyEvent.GetVirtualKeyCode() == VK_BACK)
if (keyEvent.wVirtualKeyCode == VK_BACK)
{
// The Ctrl modifier reverses the interpretation of DECBKM.
const auto backarrowMode = _inputMode.test(Mode::BackarrowKey) != keyEvent.IsCtrlPressed();
const auto backarrowMode = _inputMode.test(Mode::BackarrowKey) != WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
const auto seq = backarrowMode ? L'\x08' : L'\x7f';
// The Alt modifier adds an escape prefix.
if (keyEvent.IsAltPressed())
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED))
{
return _makeEscapedOutput(seq);
}
@ -542,7 +538,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
// When the Line Feed mode is set, a VK_RETURN key should send both CR and LF.
// When reset, we fall through to the default behavior, which is to send just
// CR, or when the Ctrl modifier is pressed, just LF.
if (keyEvent.GetVirtualKeyCode() == VK_RETURN && _inputMode.test(Mode::LineFeed))
if (keyEvent.wVirtualKeyCode == VK_RETURN && _inputMode.test(Mode::LineFeed))
{
return MakeOutput(L"\r\n");
}
@ -550,12 +546,11 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
// Many keyboard layouts have an AltGr key, which makes widely used characters accessible.
// For instance on a German keyboard layout "[" is written by pressing AltGr+8.
// Furthermore Ctrl+Alt is traditionally treated as an alternative way to AltGr by Windows.
// When AltGr is pressed, the caller needs to make sure to send us a pretranslated character in GetCharData().
// When AltGr is pressed, the caller needs to make sure to send us a pretranslated character in uChar.UnicodeChar.
// --> Strip out the AltGr flags, in order for us to not step into the Alt/Ctrl conditions below.
if (keyEvent.IsAltGrPressed())
if (WI_AreAllFlagsSet(keyEvent.dwControlKeyState, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED))
{
keyEvent.DeactivateModifierKey(ModifierKeyState::LeftCtrl);
keyEvent.DeactivateModifierKey(ModifierKeyState::RightAlt);
WI_ClearAllFlags(keyEvent.dwControlKeyState, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
}
// The Alt modifier initiates a so called "escape sequence".
@ -565,16 +560,16 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
// This section in particular handles Alt+Ctrl combinations though.
// The Ctrl modifier causes all of the char code's bits except
// for the 5 least significant ones to be zeroed out.
if (keyEvent.IsAltPressed() && keyEvent.IsCtrlPressed())
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED))
{
const auto ch = keyEvent.GetCharData();
const auto vkey = keyEvent.GetVirtualKeyCode();
const auto ch = keyEvent.uChar.UnicodeChar;
const auto vkey = keyEvent.wVirtualKeyCode;
// For Alt+Ctrl+Key messages GetCharData() usually returns 0.
// For Alt+Ctrl+Key messages uChar.UnicodeChar usually returns 0.
// Luckily the numerical values of the ASCII characters and virtual key codes
// of <Space> and A-Z, as used below, are numerically identical.
// -> Get the char from the virtual key if it's 0.
const auto ctrlAltChar = keyEvent.GetCharData() != 0 ? keyEvent.GetCharData() : keyEvent.GetVirtualKeyCode();
const auto ctrlAltChar = keyEvent.uChar.UnicodeChar != 0 ? keyEvent.uChar.UnicodeChar : keyEvent.wVirtualKeyCode;
// Alt+Ctrl acts as a substitute for AltGr on Windows.
// For instance using a German keyboard both AltGr+< and Alt+Ctrl+< produce a | (pipe) character.
@ -597,7 +592,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
}
// If a modifier key was pressed, then we need to try and send the modified sequence.
if (keyEvent.IsModifierPressed())
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, MOD_PRESSED))
{
if (auto out = _searchWithModifier(keyEvent))
{
@ -607,9 +602,9 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
// This section is similar to the Alt modifier section above,
// but handles cases without Ctrl modifiers.
if (keyEvent.IsAltPressed() && !keyEvent.IsCtrlPressed() && keyEvent.GetCharData() != 0)
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && !WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED) && keyEvent.uChar.UnicodeChar != 0)
{
return _makeEscapedOutput(keyEvent.GetCharData());
return _makeEscapedOutput(keyEvent.uChar.UnicodeChar);
}
// Pressing the control key causes all bits but the 5 least
@ -620,10 +615,10 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
// -> Send a "null input sequence" in that case.
// We don't need to handle other kinds of Ctrl combinations,
// as we rely on the caller to pretranslate those to characters for us.
if (!keyEvent.IsAltPressed() && keyEvent.IsCtrlPressed())
if (!WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED))
{
const auto ch = keyEvent.GetCharData();
const auto vkey = keyEvent.GetVirtualKeyCode();
const auto ch = keyEvent.uChar.UnicodeChar;
const auto vkey = keyEvent.wVirtualKeyCode;
// Currently, when we're called with Ctrl+@, ch will be 0, since Ctrl+@ equals a null byte.
// VkKeyScanW(0) in turn returns the vkey for the null character (ASCII @).
@ -639,7 +634,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
if (ch == UNICODE_NULL)
{
// -> Try to infer the character from the vkey.
auto mappedChar = LOWORD(OneCoreSafeMapVirtualKeyW(keyEvent.GetVirtualKeyCode(), MAPVK_VK_TO_CHAR));
auto mappedChar = LOWORD(OneCoreSafeMapVirtualKeyW(keyEvent.wVirtualKeyCode, MAPVK_VK_TO_CHAR));
if (mappedChar)
{
// Pressing the control key causes all bits but the 5 least
@ -660,9 +655,9 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
}
// If all else fails we can finally try to send the character itself if there is any.
if (keyEvent.GetCharData() != 0)
if (keyEvent.uChar.UnicodeChar != 0)
{
return _makeCharOutput(keyEvent.GetCharData());
return _makeCharOutput(keyEvent.uChar.UnicodeChar);
}
return MakeUnhandled();
@ -720,16 +715,16 @@ TerminalInput::OutputType TerminalInput::_makeEscapedOutput(const wchar_t wch)
// Turns an KEY_EVENT_RECORD into a win32-input-mode VT sequence.
// It allows us to send KEY_EVENT_RECORD data losslessly to conhost.
TerminalInput::OutputType TerminalInput::_makeWin32Output(const KeyEvent& key)
TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD& key)
{
// .uChar.UnicodeChar must be cast to an integer because we want its numerical value.
// Casting the rest to uint16_t as well doesn't hurt because that's MAX_PARAMETER_VALUE anyways.
const auto kd = gsl::narrow_cast<uint16_t>(key.IsKeyDown() ? 1 : 0);
const auto rc = gsl::narrow_cast<uint16_t>(key.GetRepeatCount());
const auto vk = gsl::narrow_cast<uint16_t>(key.GetVirtualKeyCode());
const auto sc = gsl::narrow_cast<uint16_t>(key.GetVirtualScanCode());
const auto uc = gsl::narrow_cast<uint16_t>(key.GetCharData());
const auto cs = gsl::narrow_cast<uint16_t>(key.GetActiveModifierKeys());
const auto kd = gsl::narrow_cast<uint16_t>(key.bKeyDown ? 1 : 0);
const auto rc = gsl::narrow_cast<uint16_t>(key.wRepeatCount);
const auto vk = gsl::narrow_cast<uint16_t>(key.wVirtualKeyCode);
const auto sc = gsl::narrow_cast<uint16_t>(key.wVirtualScanCode);
const auto uc = gsl::narrow_cast<uint16_t>(key.uChar.UnicodeChar);
const auto cs = gsl::narrow_cast<uint16_t>(key.dwControlKeyState);
// Sequences are formatted as follows:
//

View File

@ -1,22 +1,8 @@
/*+
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- terminalInput.hpp
Abstract:
- This serves as an adapter between virtual key input from a user and the virtual terminal sequences that are
typically emitted by an xterm-compatible console.
Author(s):
- Michael Niksa (MiNiksa) 30-Oct-2015
--*/
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "../../types/inc/IInputEvent.hpp"
namespace Microsoft::Console::VirtualTerminal
{
class TerminalInput final
@ -34,7 +20,7 @@ namespace Microsoft::Console::VirtualTerminal
static [[nodiscard]] OutputType MakeUnhandled() noexcept;
static [[nodiscard]] OutputType MakeOutput(const std::wstring_view& str);
[[nodiscard]] OutputType HandleKey(const IInputEvent* const pInEvent);
[[nodiscard]] OutputType HandleKey(const INPUT_RECORD& pInEvent);
[[nodiscard]] OutputType HandleFocus(bool focused) const;
[[nodiscard]] OutputType HandleMouse(til::point position, unsigned int button, short modifierKeyState, short delta, MouseButtonState state);
@ -89,8 +75,8 @@ namespace Microsoft::Console::VirtualTerminal
[[nodiscard]] OutputType _makeCharOutput(wchar_t ch);
static [[nodiscard]] OutputType _makeEscapedOutput(wchar_t wch);
static [[nodiscard]] OutputType _makeWin32Output(const KeyEvent& key);
static [[nodiscard]] OutputType _searchWithModifier(const KeyEvent& keyEvent);
static [[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key);
static [[nodiscard]] OutputType _searchWithModifier(const KEY_EVENT_RECORD& keyEvent);
#pragma region MouseInputState Management
// These methods are defined in mouseInputState.cpp

View File

@ -132,8 +132,11 @@ bool InputStateMachineEngine::_DoControlCharacter(const wchar_t wch, const bool
if (wch == UNICODE_ETX && !writeAlt)
{
// This is Ctrl+C, which is handled specially by the host.
const auto [keyDown, keyUp] = KeyEvent::MakePair(1, 'C', 0, UNICODE_ETX, LEFT_CTRL_PRESSED);
success = _pDispatch->WriteCtrlKey(keyDown) && _pDispatch->WriteCtrlKey(keyUp);
static constexpr auto keyDown = SynthesizeKeyEvent(true, 1, L'C', 0, UNICODE_ETX, LEFT_CTRL_PRESSED);
static constexpr auto keyUp = SynthesizeKeyEvent(false, 1, L'C', 0, UNICODE_ETX, LEFT_CTRL_PRESSED);
_pDispatch->WriteCtrlKey(keyDown);
_pDispatch->WriteCtrlKey(keyUp);
success = true;
}
else if (wch >= '\x0' && wch < '\x20')
{
@ -278,10 +281,10 @@ bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view st
// similar to TerminalInput::_SendInputSequence
if (!string.empty())
{
std::deque<std::unique_ptr<IInputEvent>> inputEvents;
InputEventQueue inputEvents;
for (const auto& wch : string)
{
inputEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
inputEvents.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
}
return _pDispatch->WriteInput(inputEvents);
}
@ -558,7 +561,7 @@ bool InputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
const short vkey,
const DWORD modifierState,
std::vector<INPUT_RECORD>& input)
InputEventQueue& input)
{
input.reserve(input.size() + 8);
@ -570,44 +573,31 @@ void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
const auto ctrl = WI_IsFlagSet(modifierState, LEFT_CTRL_PRESSED);
const auto alt = WI_IsFlagSet(modifierState, LEFT_ALT_PRESSED);
INPUT_RECORD next{ 0 };
auto next = SynthesizeKeyEvent(true, 1, 0, 0, 0, 0);
DWORD currentModifiers = 0;
if (shift)
{
WI_SetFlag(currentModifiers, SHIFT_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = TRUE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_SHIFT, MAPVK_VK_TO_VSC));
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
input.push_back(next);
}
if (alt)
{
WI_SetFlag(currentModifiers, LEFT_ALT_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = TRUE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_MENU;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_MENU, MAPVK_VK_TO_VSC));
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
input.push_back(next);
}
if (ctrl)
{
WI_SetFlag(currentModifiers, LEFT_CTRL_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = TRUE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_CONTROL, MAPVK_VK_TO_VSC));
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
input.push_back(next);
}
@ -616,40 +606,30 @@ void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
// through on the KeyPress.
_GetSingleKeypress(wch, vkey, modifierState, input);
next.Event.KeyEvent.bKeyDown = FALSE;
if (ctrl)
{
WI_ClearFlag(currentModifiers, LEFT_CTRL_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = FALSE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_CONTROL, MAPVK_VK_TO_VSC));
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
input.push_back(next);
}
if (alt)
{
WI_ClearFlag(currentModifiers, LEFT_ALT_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = FALSE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_MENU;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_MENU, MAPVK_VK_TO_VSC));
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
input.push_back(next);
}
if (shift)
{
WI_ClearFlag(currentModifiers, SHIFT_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = FALSE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_SHIFT, MAPVK_VK_TO_VSC));
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
input.push_back(next);
}
}
@ -668,21 +648,14 @@ void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
void InputStateMachineEngine::_GetSingleKeypress(const wchar_t wch,
const short vkey,
const DWORD modifierState,
std::vector<INPUT_RECORD>& input)
InputEventQueue& input)
{
input.reserve(input.size() + 2);
INPUT_RECORD rec;
const auto sc = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(vkey, MAPVK_VK_TO_VSC));
auto rec = SynthesizeKeyEvent(true, 1, vkey, sc, wch, modifierState);
rec.EventType = KEY_EVENT;
rec.Event.KeyEvent.bKeyDown = TRUE;
rec.Event.KeyEvent.dwControlKeyState = modifierState;
rec.Event.KeyEvent.wRepeatCount = 1;
rec.Event.KeyEvent.wVirtualKeyCode = vkey;
rec.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(vkey, MAPVK_VK_TO_VSC));
rec.Event.KeyEvent.uChar.UnicodeChar = wch;
input.push_back(rec);
rec.Event.KeyEvent.bKeyDown = FALSE;
input.push_back(rec);
}
@ -701,10 +674,8 @@ void InputStateMachineEngine::_GetSingleKeypress(const wchar_t wch,
bool InputStateMachineEngine::_WriteSingleKey(const wchar_t wch, const short vkey, const DWORD modifierState)
{
// At most 8 records - 2 for each of shift,ctrl,alt up and down, and 2 for the actual key up and down.
std::vector<INPUT_RECORD> input;
_GenerateWrappedSequence(wch, vkey, modifierState, input);
auto inputEvents = IInputEvent::Create(std::span{ input });
InputEventQueue inputEvents;
_GenerateWrappedSequence(wch, vkey, modifierState, inputEvents);
return _pDispatch->WriteInput(inputEvents);
}
@ -734,18 +705,8 @@ bool InputStateMachineEngine::_WriteSingleKey(const short vkey, const DWORD modi
// - true iff we successfully wrote the keypress to the input callback.
bool InputStateMachineEngine::_WriteMouseEvent(const til::point uiPos, const DWORD buttonState, const DWORD controlKeyState, const DWORD eventFlags)
{
INPUT_RECORD rgInput;
rgInput.EventType = MOUSE_EVENT;
rgInput.Event.MouseEvent.dwMousePosition.X = ::base::saturated_cast<short>(uiPos.x);
rgInput.Event.MouseEvent.dwMousePosition.Y = ::base::saturated_cast<short>(uiPos.y);
rgInput.Event.MouseEvent.dwButtonState = buttonState;
rgInput.Event.MouseEvent.dwControlKeyState = controlKeyState;
rgInput.Event.MouseEvent.dwEventFlags = eventFlags;
// pack and write input record
// 1 record - the modifiers don't get their own events
auto inputEvents = IInputEvent::Create(std::span{ &rgInput, 1 });
return _pDispatch->WriteInput(inputEvents);
const auto rgInput = SynthesizeMouseEvent(uiPos, buttonState, controlKeyState, eventFlags);
return _pDispatch->WriteInput({ &rgInput, 1 });
}
// Method Description:
@ -1127,7 +1088,7 @@ bool InputStateMachineEngine::_GetWindowManipulationType(const std::span<const s
// - parameters: the list of numbers to parse into values for the KeyEvent.
// Return Value:
// - The deserialized KeyEvent.
KeyEvent InputStateMachineEngine::_GenerateWin32Key(const VTParameters parameters)
INPUT_RECORD InputStateMachineEngine::_GenerateWin32Key(const VTParameters& parameters)
{
// Sequences are formatted as follows:
//
@ -1140,13 +1101,11 @@ KeyEvent InputStateMachineEngine::_GenerateWin32Key(const VTParameters parameter
// Kd: the value of bKeyDown - either a '0' or '1'. If omitted, defaults to '0'.
// Cs: the value of dwControlKeyState - any number. If omitted, defaults to '0'.
// Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'.
auto key = KeyEvent();
key.SetVirtualKeyCode(::base::saturated_cast<WORD>(parameters.at(0).value_or(0)));
key.SetVirtualScanCode(::base::saturated_cast<WORD>(parameters.at(1).value_or(0)));
key.SetCharData(::base::saturated_cast<wchar_t>(parameters.at(2).value_or(0)));
key.SetKeyDown(parameters.at(3).value_or(0));
key.SetActiveModifierKeys(::base::saturated_cast<DWORD>(parameters.at(4).value_or(0)));
key.SetRepeatCount(::base::saturated_cast<WORD>(parameters.at(5).value_or(1)));
return key;
return SynthesizeKeyEvent(
parameters.at(3).value_or(0),
::base::saturated_cast<uint16_t>(parameters.at(5).value_or(1)),
::base::saturated_cast<uint16_t>(parameters.at(0).value_or(0)),
::base::saturated_cast<uint16_t>(parameters.at(1).value_or(0)),
::base::saturated_cast<wchar_t>(parameters.at(2).value_or(0)),
::base::saturated_cast<uint32_t>(parameters.at(4).value_or(0)));
}

View File

@ -197,17 +197,17 @@ namespace Microsoft::Console::VirtualTerminal
void _GenerateWrappedSequence(const wchar_t wch,
const short vkey,
const DWORD modifierState,
std::vector<INPUT_RECORD>& input);
InputEventQueue& input);
void _GetSingleKeypress(const wchar_t wch,
const short vkey,
const DWORD modifierState,
std::vector<INPUT_RECORD>& input);
InputEventQueue& input);
bool _GetWindowManipulationType(const std::span<const size_t> parameters,
unsigned int& function) const noexcept;
KeyEvent _GenerateWin32Key(const VTParameters parameters);
static INPUT_RECORD _GenerateWin32Key(const VTParameters& parameters);
bool _DoControlCharacter(const wchar_t wch, const bool writeAlt);

View File

@ -75,11 +75,10 @@ public:
{
}
void RoundtripTerminalInputCallback(_In_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
void RoundtripTerminalInputCallback(_In_ const std::span<const INPUT_RECORD>& inputRecords)
{
// Take all the characters out of the input records here, and put them into
// the input state machine.
auto inputRecords = IInputEvent::ToInputRecords(inEvents);
std::wstring vtseq = L"";
for (auto& inRec : inputRecords)
{
@ -96,10 +95,8 @@ public:
Log::Comment(L"String processed");
}
void TestInputCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents)
void TestInputCallback(const std::span<const INPUT_RECORD>& records)
{
auto records = IInputEvent::ToInputRecords(inEvents);
// This callback doesn't work super well for the Ctrl+C iteration of the
// C0Test. For ^C, we always send a keydown and a key up event, however,
// both calls to WriteCtrlKey happen in one single call to
@ -146,10 +143,8 @@ public:
vExpectedInput.clear();
}
void TestInputStringCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents)
void TestInputStringCallback(const std::span<const INPUT_RECORD>& records)
{
auto records = IInputEvent::ToInputRecords(inEvents);
for (auto expected : vExpectedInput)
{
Log::Comment(
@ -211,9 +206,9 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest
TestState testState;
void RoundtripTerminalInputCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents);
void TestInputCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents);
void TestInputStringCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents);
void RoundtripTerminalInputCallback(const std::span<const INPUT_RECORD>& inEvents);
void TestInputCallback(const std::span<const INPUT_RECORD>& inEvents);
void TestInputStringCallback(const std::span<const INPUT_RECORD>& inEvents);
std::wstring GenerateSgrMouseSequence(const CsiMouseButtonCodes button,
const unsigned short modifiers,
const til::point position,
@ -318,11 +313,11 @@ void InputEngineTest::VerifyExpectedInputDrained()
class Microsoft::Console::VirtualTerminal::TestInteractDispatch final : public IInteractDispatch
{
public:
TestInteractDispatch(_In_ std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> pfn,
TestInteractDispatch(_In_ std::function<void(const std::span<const INPUT_RECORD>&)> pfn,
_In_ TestState* testState);
virtual bool WriteInput(_In_ std::deque<std::unique_ptr<IInputEvent>>& inputEvents) override;
virtual bool WriteInput(_In_ const std::span<const INPUT_RECORD>& inputEvents) override;
virtual bool WriteCtrlKey(const KeyEvent& event) override;
virtual bool WriteCtrlKey(const INPUT_RECORD& event) override;
virtual bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const VTParameter parameter1,
const VTParameter parameter2) override; // DTTERM_WindowManipulation
@ -336,29 +331,27 @@ public:
virtual bool FocusChanged(const bool focused) const override;
private:
std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> _pfnWriteInputCallback;
std::function<void(const std::span<const INPUT_RECORD>&)> _pfnWriteInputCallback;
TestState* _testState; // non-ownership pointer
};
TestInteractDispatch::TestInteractDispatch(_In_ std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> pfn,
TestInteractDispatch::TestInteractDispatch(_In_ std::function<void(const std::span<const INPUT_RECORD>&)> pfn,
_In_ TestState* testState) :
_pfnWriteInputCallback(pfn),
_testState(testState)
{
}
bool TestInteractDispatch::WriteInput(_In_ std::deque<std::unique_ptr<IInputEvent>>& inputEvents)
bool TestInteractDispatch::WriteInput(_In_ const std::span<const INPUT_RECORD>& inputEvents)
{
_pfnWriteInputCallback(inputEvents);
return true;
}
bool TestInteractDispatch::WriteCtrlKey(const KeyEvent& event)
bool TestInteractDispatch::WriteCtrlKey(const INPUT_RECORD& event)
{
VERIFY_IS_TRUE(_testState->_expectSendCtrlC);
std::deque<std::unique_ptr<IInputEvent>> inputEvents;
inputEvents.push_back(std::make_unique<KeyEvent>(event));
return WriteInput(inputEvents);
return WriteInput({ &event, 1 });
}
bool TestInteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulationType function,
@ -374,16 +367,13 @@ bool TestInteractDispatch::WindowManipulation(const DispatchTypes::WindowManipul
bool TestInteractDispatch::WriteString(const std::wstring_view string)
{
std::deque<std::unique_ptr<IInputEvent>> keyEvents;
InputEventQueue keyEvents;
for (const auto& wch : string)
{
// We're forcing the translation to CP_USA, so that it'll be constant
// regardless of the CP the test is running in
auto convertedEvents = Microsoft::Console::Interactivity::CharToKeyEvents(wch, CP_USA);
std::move(convertedEvents.begin(),
convertedEvents.end(),
std::back_inserter(keyEvents));
Microsoft::Console::Interactivity::CharToKeyEvents(wch, CP_USA, keyEvents);
}
return WriteInput(keyEvents);
@ -966,12 +956,12 @@ void InputEngineTest::AltIntermediateTest()
// Create the callback that's fired when the state machine wants to write
// input. We'll take the events and put them straight into the
// TerminalInput.
auto pfnInputStateMachineCallback = [&](std::deque<std::unique_ptr<IInputEvent>>& inEvents) {
auto pfnInputStateMachineCallback = [&](const std::span<const INPUT_RECORD>& inEvents) {
for (auto& ev : inEvents)
{
if (const auto out = terminalInput.HandleKey(ev.get()))
if (const auto str = terminalInput.HandleKey(ev))
{
translation.append(*out);
translation.append(*str);
}
}
};
@ -1474,73 +1464,73 @@ void InputEngineTest::TestWin32InputParsing()
{
std::vector<VTParameter> params{ 1 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(0, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\0', key.GetCharData());
VERIFY_ARE_EQUAL(false, key.IsKeyDown());
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(0, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\0', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(FALSE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
}
{
std::vector<VTParameter> params{ 1, 2 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\0', key.GetCharData());
VERIFY_ARE_EQUAL(false, key.IsKeyDown());
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\0', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(FALSE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
}
{
std::vector<VTParameter> params{ 1, 2, 3 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
VERIFY_ARE_EQUAL(false, key.IsKeyDown());
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(FALSE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
}
{
std::vector<VTParameter> params{ 1, 2, 3, 4 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
VERIFY_ARE_EQUAL(true, key.IsKeyDown());
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
}
{
std::vector<VTParameter> params{ 1, 2, 3, 1 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
VERIFY_ARE_EQUAL(true, key.IsKeyDown());
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
}
{
std::vector<VTParameter> params{ 1, 2, 3, 4, 5 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
VERIFY_ARE_EQUAL(true, key.IsKeyDown());
VERIFY_ARE_EQUAL(0x5u, key.GetActiveModifierKeys());
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
VERIFY_ARE_EQUAL(0x5u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
}
{
std::vector<VTParameter> params{ 1, 2, 3, 4, 5, 6 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
VERIFY_ARE_EQUAL(true, key.IsKeyDown());
VERIFY_ARE_EQUAL(0x5u, key.GetActiveModifierKeys());
VERIFY_ARE_EQUAL(6, key.GetRepeatCount());
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
VERIFY_ARE_EQUAL(0x5u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(6, key.wRepeatCount);
}
}
@ -1580,26 +1570,26 @@ void InputEngineTest::TestWin32InputOptionals()
provideRepeatCount ? 6 : 0,
};
auto key = engine->_GenerateWin32Key({ params.data(), numParams });
auto key = engine->_GenerateWin32Key({ params.data(), numParams }).Event.KeyEvent;
VERIFY_ARE_EQUAL((provideVirtualKeyCode && numParams > 0) ? 1 : 0,
key.GetVirtualKeyCode());
key.wVirtualKeyCode);
VERIFY_ARE_EQUAL((provideVirtualScanCode && numParams > 1) ? 2 : 0,
key.GetVirtualScanCode());
key.wVirtualScanCode);
VERIFY_ARE_EQUAL((provideCharData && numParams > 2) ? L'\x03' : L'\0',
key.GetCharData());
VERIFY_ARE_EQUAL((provideKeyDown && numParams > 3) ? true : false,
key.IsKeyDown());
key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL((provideKeyDown && numParams > 3) ? TRUE : FALSE,
key.bKeyDown);
VERIFY_ARE_EQUAL((provideActiveModifierKeys && numParams > 4) ? 5u : 0u,
key.GetActiveModifierKeys());
key.dwControlKeyState);
if (numParams == 6)
{
VERIFY_ARE_EQUAL((provideRepeatCount) ? 6 : 0,
key.GetRepeatCount());
key.wRepeatCount);
}
else
{
VERIFY_ARE_EQUAL((provideRepeatCount && numParams > 5) ? 6 : 1,
key.GetRepeatCount());
key.wRepeatCount);
}
}
}

View File

@ -1,38 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// BODGY: GH#13238
//
// It appears that some applications (libuv) like to send a FOCUS_EVENT_RECORD
// as a way to jiggle the input handle. Focus events really shouldn't ever be
// sent via the API, they don't really do anything internally. However, focus
// events in the input buffer do get translated by the TerminalInput to VT
// sequences if we're in the right input mode.
//
// To not prevent libuv from jiggling the handle with a focus event, and also
// make sure that we don't erroneously translate that to a sequence of
// characters, we're going to filter out focus events that came from the API
// when translating to VT.
#include "precomp.h"
#include "inc/IInputEvent.hpp"
FocusEvent::~FocusEvent() = default;
INPUT_RECORD FocusEvent::ToInputRecord() const noexcept
{
INPUT_RECORD record{ 0 };
record.EventType = FOCUS_EVENT;
record.Event.FocusEvent.bSetFocus = !!_focus;
return record;
}
InputEventType FocusEvent::EventType() const noexcept
{
return InputEventType::FocusEvent;
}
void FocusEvent::SetFocus(const bool focus) noexcept
{
_focus = focus;
}

View File

@ -1,68 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inc/IInputEvent.hpp"
#include <unordered_map>
std::unique_ptr<IInputEvent> IInputEvent::Create(const INPUT_RECORD& record)
{
switch (record.EventType)
{
case KEY_EVENT:
return std::make_unique<KeyEvent>(record.Event.KeyEvent);
case MOUSE_EVENT:
return std::make_unique<MouseEvent>(record.Event.MouseEvent);
case WINDOW_BUFFER_SIZE_EVENT:
return std::make_unique<WindowBufferSizeEvent>(record.Event.WindowBufferSizeEvent);
case MENU_EVENT:
return std::make_unique<MenuEvent>(record.Event.MenuEvent);
case FOCUS_EVENT:
return std::make_unique<FocusEvent>(record.Event.FocusEvent);
default:
THROW_HR(E_INVALIDARG);
}
}
std::deque<std::unique_ptr<IInputEvent>> IInputEvent::Create(std::span<const INPUT_RECORD> records)
{
std::deque<std::unique_ptr<IInputEvent>> outEvents;
for (const auto& record : records)
{
outEvents.push_back(Create(record));
}
return outEvents;
}
// Routine Description:
// - Converts std::deque<INPUT_RECORD> to std::deque<std::unique_ptr<IInputEvent>>
// Arguments:
// - inRecords - records to convert
// Return Value:
// - std::deque of IInputEvents on success. Will throw exception on failure.
std::deque<std::unique_ptr<IInputEvent>> IInputEvent::Create(const std::deque<INPUT_RECORD>& records)
{
std::deque<std::unique_ptr<IInputEvent>> outEvents;
for (const auto& record : records)
{
auto event = Create(record);
outEvents.push_back(std::move(event));
}
return outEvents;
}
std::vector<INPUT_RECORD> IInputEvent::ToInputRecords(const std::deque<std::unique_ptr<IInputEvent>>& events)
{
std::vector<INPUT_RECORD> records;
records.reserve(events.size());
for (auto& evt : events)
{
records.push_back(evt->ToInputRecord());
}
return records;
}

View File

@ -1,113 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inc/IInputEvent.hpp"
#include <string>
std::wostream& operator<<(std::wostream& stream, const IInputEvent* const pEvent)
{
if (pEvent == nullptr)
{
return stream << L"nullptr";
}
try
{
switch (pEvent->EventType())
{
case InputEventType::KeyEvent:
return stream << static_cast<const KeyEvent* const>(pEvent);
case InputEventType::MouseEvent:
return stream << static_cast<const MouseEvent* const>(pEvent);
case InputEventType::WindowBufferSizeEvent:
return stream << static_cast<const WindowBufferSizeEvent* const>(pEvent);
case InputEventType::MenuEvent:
return stream << static_cast<const MenuEvent* const>(pEvent);
case InputEventType::FocusEvent:
return stream << static_cast<const FocusEvent* const>(pEvent);
default:
return stream << L"IInputEvent()";
}
}
catch (...)
{
return stream << L"IInputEvent stream error";
}
}
std::wostream& operator<<(std::wostream& stream, const KeyEvent* const pKeyEvent)
{
if (pKeyEvent == nullptr)
{
return stream << L"nullptr";
}
std::wstring keyMotion = pKeyEvent->_keyDown ? L"keyDown" : L"keyUp";
std::wstring charData = { pKeyEvent->_charData };
if (pKeyEvent->_charData == L'\0')
{
charData = L"null";
}
// clang-format off
return stream << L"KeyEvent(" <<
keyMotion << L", " <<
L"repeat: " << pKeyEvent->_repeatCount << L", " <<
L"keyCode: " << pKeyEvent->_virtualKeyCode << L", " <<
L"scanCode: " << pKeyEvent->_virtualScanCode << L", " <<
L"char: " << charData << L", " <<
L"mods: " << pKeyEvent->GetActiveModifierKeys() << L")";
// clang-format on
}
std::wostream& operator<<(std::wostream& stream, const MouseEvent* const pMouseEvent)
{
if (pMouseEvent == nullptr)
{
return stream << L"nullptr";
}
// clang-format off
return stream << L"MouseEvent(" <<
L"X: " << pMouseEvent->_position.x << L", " <<
L"Y: " << pMouseEvent->_position.y << L", " <<
L"buttons: " << pMouseEvent->_buttonState << L", " <<
L"mods: " << pMouseEvent->_activeModifierKeys << L", " <<
L"events: " << pMouseEvent->_eventFlags << L")";
// clang-format on
}
std::wostream& operator<<(std::wostream& stream, const WindowBufferSizeEvent* const pEvent)
{
if (pEvent == nullptr)
{
return stream << L"nullptr";
}
// clang-format off
return stream << L"WindowbufferSizeEvent(" <<
L"X: " << pEvent->_size.width << L", " <<
L"Y: " << pEvent->_size.height << L")";
// clang-format on
}
std::wostream& operator<<(std::wostream& stream, const MenuEvent* const pMenuEvent)
{
if (pMenuEvent == nullptr)
{
return stream << L"nullptr";
}
return stream << L"MenuEvent(" << L"CommandId" << pMenuEvent->_commandId << L")";
}
std::wostream& operator<<(std::wostream& stream, const FocusEvent* const pFocusEvent)
{
if (pFocusEvent == nullptr)
{
return stream << L"nullptr";
}
return stream << L"FocusEvent(" << L"focus" << pFocusEvent->_focus << L")";
}

View File

@ -1,189 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inc/IInputEvent.hpp"
KeyEvent::~KeyEvent() = default;
INPUT_RECORD KeyEvent::ToInputRecord() const noexcept
{
INPUT_RECORD record{};
record.EventType = KEY_EVENT;
record.Event.KeyEvent.bKeyDown = !!_keyDown;
record.Event.KeyEvent.wRepeatCount = _repeatCount;
record.Event.KeyEvent.wVirtualKeyCode = _virtualKeyCode;
record.Event.KeyEvent.wVirtualScanCode = _virtualScanCode;
record.Event.KeyEvent.uChar.UnicodeChar = _charData;
record.Event.KeyEvent.dwControlKeyState = GetActiveModifierKeys();
return record;
}
InputEventType KeyEvent::EventType() const noexcept
{
return InputEventType::KeyEvent;
}
void KeyEvent::SetKeyDown(const bool keyDown) noexcept
{
_keyDown = keyDown;
}
void KeyEvent::SetRepeatCount(const WORD repeatCount) noexcept
{
_repeatCount = repeatCount;
}
void KeyEvent::SetVirtualKeyCode(const WORD virtualKeyCode) noexcept
{
_virtualKeyCode = virtualKeyCode;
}
void KeyEvent::SetVirtualScanCode(const WORD virtualScanCode) noexcept
{
_virtualScanCode = virtualScanCode;
}
void KeyEvent::SetCharData(const char character) noexcept
{
// With MSVC char is signed by default and the conversion to wchar_t (unsigned) would turn negative
// chars into very large wchar_t values. While this doesn't pose a problem per se (even with such sign
// extension, the lower 8 bit stay the same), it makes debugging and reading key events more difficult.
_charData = til::as_unsigned(character);
}
void KeyEvent::SetCharData(const wchar_t character) noexcept
{
_charData = character;
}
void KeyEvent::SetActiveModifierKeys(const DWORD activeModifierKeys) noexcept
{
_activeModifierKeys = static_cast<KeyEvent::Modifiers>(activeModifierKeys);
}
void KeyEvent::DeactivateModifierKey(const ModifierKeyState modifierKey) noexcept
{
const auto bitFlag = ToConsoleControlKeyFlag(modifierKey);
auto keys = GetActiveModifierKeys();
WI_ClearAllFlags(keys, bitFlag);
SetActiveModifierKeys(keys);
}
void KeyEvent::ActivateModifierKey(const ModifierKeyState modifierKey) noexcept
{
const auto bitFlag = ToConsoleControlKeyFlag(modifierKey);
auto keys = GetActiveModifierKeys();
WI_SetAllFlags(keys, bitFlag);
SetActiveModifierKeys(keys);
}
bool KeyEvent::DoActiveModifierKeysMatch(const std::unordered_set<ModifierKeyState>& consoleModifiers) const noexcept
{
DWORD consoleBits = 0;
for (const auto& mod : consoleModifiers)
{
WI_SetAllFlags(consoleBits, ToConsoleControlKeyFlag(mod));
}
return consoleBits == GetActiveModifierKeys();
}
// Routine Description:
// - checks if this key event is a special key for line editing
// Arguments:
// - none
// Return Value:
// - true if this key has special relevance to line editing, false otherwise
bool KeyEvent::IsCommandLineEditingKey() const noexcept
{
if (!IsAltPressed() && !IsCtrlPressed())
{
switch (GetVirtualKeyCode())
{
case VK_ESCAPE:
case VK_PRIOR:
case VK_NEXT:
case VK_END:
case VK_HOME:
case VK_LEFT:
case VK_UP:
case VK_RIGHT:
case VK_DOWN:
case VK_INSERT:
case VK_DELETE:
case VK_F1:
case VK_F2:
case VK_F3:
case VK_F4:
case VK_F5:
case VK_F6:
case VK_F7:
case VK_F8:
case VK_F9:
return true;
default:
break;
}
}
if (IsCtrlPressed())
{
switch (GetVirtualKeyCode())
{
case VK_END:
case VK_HOME:
case VK_LEFT:
case VK_RIGHT:
return true;
default:
break;
}
}
if (IsAltPressed())
{
switch (GetVirtualKeyCode())
{
case VK_F7:
case VK_F10:
return true;
default:
break;
}
}
return false;
}
// Routine Description:
// - checks if this key event is a special key for popups
// Arguments:
// - None
// Return Value:
// - true if this key has special relevance to popups, false otherwise
bool KeyEvent::IsPopupKey() const noexcept
{
if (!IsAltPressed() && !IsCtrlPressed())
{
switch (GetVirtualKeyCode())
{
case VK_ESCAPE:
case VK_PRIOR:
case VK_NEXT:
case VK_END:
case VK_HOME:
case VK_LEFT:
case VK_UP:
case VK_RIGHT:
case VK_DOWN:
case VK_F2:
case VK_F4:
case VK_F7:
case VK_F9:
case VK_DELETE:
return true;
default:
break;
}
}
return false;
}

View File

@ -1,25 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inc/IInputEvent.hpp"
MenuEvent::~MenuEvent() = default;
INPUT_RECORD MenuEvent::ToInputRecord() const noexcept
{
INPUT_RECORD record{ 0 };
record.EventType = MENU_EVENT;
record.Event.MenuEvent.dwCommandId = _commandId;
return record;
}
InputEventType MenuEvent::EventType() const noexcept
{
return InputEventType::MenuEvent;
}
void MenuEvent::SetCommandId(const UINT commandId) noexcept
{
_commandId = commandId;
}

View File

@ -1,137 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inc/IInputEvent.hpp"
#include <unordered_map>
// Routine Description:
// - checks if flag is present in flags
// Arguments:
// - flags - bit pattern to check for flag
// - flag - bit pattern to search for
// Return Value:
// - true if flag is present in flags
// Note:
// - The wil version of IsFlagSet is only to operate on values that
// are compile time constants. This will work with runtime calculated
// values.
constexpr bool RuntimeIsFlagSet(const DWORD flags, const DWORD flag) noexcept
{
return !!(flags & flag);
}
std::unordered_set<ModifierKeyState> FromVkKeyScan(const short vkKeyScanFlags)
{
std::unordered_set<ModifierKeyState> keyState;
switch (vkKeyScanFlags)
{
case VkKeyScanModState::None:
break;
case VkKeyScanModState::ShiftPressed:
keyState.insert(ModifierKeyState::Shift);
break;
case VkKeyScanModState::CtrlPressed:
keyState.insert(ModifierKeyState::LeftCtrl);
keyState.insert(ModifierKeyState::RightCtrl);
break;
case VkKeyScanModState::ShiftAndCtrlPressed:
keyState.insert(ModifierKeyState::Shift);
keyState.insert(ModifierKeyState::LeftCtrl);
keyState.insert(ModifierKeyState::RightCtrl);
break;
case VkKeyScanModState::AltPressed:
keyState.insert(ModifierKeyState::LeftAlt);
keyState.insert(ModifierKeyState::RightAlt);
break;
case VkKeyScanModState::ShiftAndAltPressed:
keyState.insert(ModifierKeyState::Shift);
keyState.insert(ModifierKeyState::LeftAlt);
keyState.insert(ModifierKeyState::RightAlt);
break;
case VkKeyScanModState::CtrlAndAltPressed:
keyState.insert(ModifierKeyState::LeftCtrl);
keyState.insert(ModifierKeyState::RightCtrl);
keyState.insert(ModifierKeyState::LeftAlt);
keyState.insert(ModifierKeyState::RightAlt);
break;
case VkKeyScanModState::ModPressed:
keyState.insert(ModifierKeyState::Shift);
keyState.insert(ModifierKeyState::LeftCtrl);
keyState.insert(ModifierKeyState::RightCtrl);
keyState.insert(ModifierKeyState::LeftAlt);
keyState.insert(ModifierKeyState::RightAlt);
break;
default:
THROW_HR(E_INVALIDARG);
break;
}
return keyState;
}
using ModifierKeyStateMapping = std::pair<ModifierKeyState, DWORD>;
constexpr static ModifierKeyStateMapping ModifierKeyStateTranslationTable[] = {
{ ModifierKeyState::RightAlt, RIGHT_ALT_PRESSED },
{ ModifierKeyState::LeftAlt, LEFT_ALT_PRESSED },
{ ModifierKeyState::RightCtrl, RIGHT_CTRL_PRESSED },
{ ModifierKeyState::LeftCtrl, LEFT_CTRL_PRESSED },
{ ModifierKeyState::Shift, SHIFT_PRESSED },
{ ModifierKeyState::NumLock, NUMLOCK_ON },
{ ModifierKeyState::ScrollLock, SCROLLLOCK_ON },
{ ModifierKeyState::CapsLock, CAPSLOCK_ON },
{ ModifierKeyState::EnhancedKey, ENHANCED_KEY },
{ ModifierKeyState::NlsDbcsChar, NLS_DBCSCHAR },
{ ModifierKeyState::NlsAlphanumeric, NLS_ALPHANUMERIC },
{ ModifierKeyState::NlsKatakana, NLS_KATAKANA },
{ ModifierKeyState::NlsHiragana, NLS_HIRAGANA },
{ ModifierKeyState::NlsRoman, NLS_ROMAN },
{ ModifierKeyState::NlsImeConversion, NLS_IME_CONVERSION },
{ ModifierKeyState::AltNumpad, ALTNUMPAD_BIT },
{ ModifierKeyState::NlsImeDisable, NLS_IME_DISABLE }
};
static_assert(size(ModifierKeyStateTranslationTable) == static_cast<int>(ModifierKeyState::ENUM_COUNT),
"ModifierKeyStateTranslationTable must have a valid mapping for each modifier value");
// Routine Description:
// - Expands legacy control keys bitsets into a stl set
// Arguments:
// - flags - legacy bitset to expand
// Return Value:
// - set of ModifierKeyState values that represent flags
std::unordered_set<ModifierKeyState> FromConsoleControlKeyFlags(const DWORD flags)
{
std::unordered_set<ModifierKeyState> keyStates;
for (const auto& mapping : ModifierKeyStateTranslationTable)
{
if (RuntimeIsFlagSet(flags, mapping.second))
{
keyStates.insert(mapping.first);
}
}
return keyStates;
}
// Routine Description:
// - Converts ModifierKeyState back to the bizarre console bitflag associated with it.
// Arguments:
// - modifierKey - modifier to convert
// Return Value:
// - console bitflag associated with modifierKey
DWORD ToConsoleControlKeyFlag(const ModifierKeyState modifierKey) noexcept
{
for (const auto& mapping : ModifierKeyStateTranslationTable)
{
if (mapping.first == modifierKey)
{
return mapping.second;
}
}
return 0;
}

View File

@ -1,43 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inc/IInputEvent.hpp"
MouseEvent::~MouseEvent() = default;
INPUT_RECORD MouseEvent::ToInputRecord() const noexcept
{
INPUT_RECORD record{ 0 };
record.EventType = MOUSE_EVENT;
record.Event.MouseEvent.dwMousePosition.X = ::base::saturated_cast<short>(_position.x);
record.Event.MouseEvent.dwMousePosition.Y = ::base::saturated_cast<short>(_position.y);
record.Event.MouseEvent.dwButtonState = _buttonState;
record.Event.MouseEvent.dwControlKeyState = _activeModifierKeys;
record.Event.MouseEvent.dwEventFlags = _eventFlags;
return record;
}
InputEventType MouseEvent::EventType() const noexcept
{
return InputEventType::MouseEvent;
}
void MouseEvent::SetPosition(const til::point position) noexcept
{
_position = position;
}
void MouseEvent::SetButtonState(const DWORD buttonState) noexcept
{
_buttonState = buttonState;
}
void MouseEvent::SetActiveModifierKeys(const DWORD activeModifierKeys) noexcept
{
_activeModifierKeys = activeModifierKeys;
}
void MouseEvent::SetEventFlags(const DWORD eventFlags) noexcept
{
_eventFlags = eventFlags;
}

View File

@ -1,26 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inc/IInputEvent.hpp"
WindowBufferSizeEvent::~WindowBufferSizeEvent() = default;
INPUT_RECORD WindowBufferSizeEvent::ToInputRecord() const noexcept
{
INPUT_RECORD record{ 0 };
record.EventType = WINDOW_BUFFER_SIZE_EVENT;
record.Event.WindowBufferSizeEvent.dwSize.X = ::base::saturated_cast<short>(_size.width);
record.Event.WindowBufferSizeEvent.dwSize.Y = ::base::saturated_cast<short>(_size.height);
return record;
}
InputEventType WindowBufferSizeEvent::EventType() const noexcept
{
return InputEventType::WindowBufferSizeEvent;
}
void WindowBufferSizeEvent::SetSize(const til::size size) noexcept
{
_size = size;
}

View File

@ -1,553 +1,89 @@
/*++
Copyright (c) Microsoft Corporation
Module Name:
- IInputEvent.hpp
Abstract:
- Internal representation of public INPUT_RECORD struct.
Author:
- Austin Diviness (AustDi) 18-Aug-2017
--*/
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <wil/common.h>
#include <wil/resource.h>
#ifndef ALTNUMPAD_BIT
// from winconp.h
#define ALTNUMPAD_BIT 0x04000000 // AltNumpad OEM char (copied from ntuser/inc/kbd.h)
#endif
#include <wtypes.h>
#include <unordered_set>
#include <memory>
#include <deque>
#include <ostream>
enum class InputEventType
{
KeyEvent,
MouseEvent,
WindowBufferSizeEvent,
MenuEvent,
FocusEvent
};
class IInputEvent
{
public:
static std::unique_ptr<IInputEvent> Create(const INPUT_RECORD& record);
static std::deque<std::unique_ptr<IInputEvent>> Create(std::span<const INPUT_RECORD> records);
static std::deque<std::unique_ptr<IInputEvent>> Create(const std::deque<INPUT_RECORD>& records);
static std::vector<INPUT_RECORD> ToInputRecords(const std::deque<std::unique_ptr<IInputEvent>>& events);
virtual ~IInputEvent() = 0;
IInputEvent() = default;
IInputEvent(const IInputEvent&) = default;
IInputEvent(IInputEvent&&) = default;
IInputEvent& operator=(const IInputEvent&) & = default;
IInputEvent& operator=(IInputEvent&&) & = default;
virtual INPUT_RECORD ToInputRecord() const noexcept = 0;
virtual InputEventType EventType() const noexcept = 0;
#ifdef UNIT_TESTING
friend std::wostream& operator<<(std::wostream& stream, const IInputEvent* const pEvent);
#endif
};
inline IInputEvent::~IInputEvent() = default;
using InputEventQueue = std::deque<std::unique_ptr<IInputEvent>>;
#ifdef UNIT_TESTING
std::wostream& operator<<(std::wostream& stream, const IInputEvent* pEvent);
#endif
#include <til/small_vector.h>
#define ALT_PRESSED (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)
#define CTRL_PRESSED (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)
#define MOD_PRESSED (SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED)
// Note taken from VkKeyScan docs (https://msdn.microsoft.com/en-us/library/windows/desktop/ms646329(v=vs.85).aspx):
// For keyboard layouts that use the right-hand ALT key as a shift key
// (for example, the French keyboard layout), the shift state is
// represented by the value 6, because the right-hand ALT key is
// converted internally into CTRL+ALT.
struct VkKeyScanModState
{
static const byte None = 0;
static const byte ShiftPressed = 1;
static const byte CtrlPressed = 2;
static const byte ShiftAndCtrlPressed = ShiftPressed | CtrlPressed;
static const byte AltPressed = 4;
static const byte ShiftAndAltPressed = ShiftPressed | AltPressed;
static const byte CtrlAndAltPressed = CtrlPressed | AltPressed;
static const byte ModPressed = ShiftPressed | CtrlPressed | AltPressed;
};
using InputEventQueue = til::small_vector<INPUT_RECORD, 16>;
enum class ModifierKeyState
// The following abstractions exist to hopefully make it easier to migrate
// to a different underlying InputEvent type if we ever choose to do so.
// (As unlikely as that is, given that the main user is the console API which will always use INPUT_RECORD.)
constexpr INPUT_RECORD SynthesizeKeyEvent(bool bKeyDown, uint16_t wRepeatCount, uint16_t wVirtualKeyCode, uint16_t wVirtualScanCode, wchar_t UnicodeChar, uint32_t dwControlKeyState)
{
RightAlt,
LeftAlt,
RightCtrl,
LeftCtrl,
Shift,
NumLock,
ScrollLock,
CapsLock,
EnhancedKey,
NlsDbcsChar,
NlsAlphanumeric,
NlsKatakana,
NlsHiragana,
NlsRoman,
NlsImeConversion,
AltNumpad,
NlsImeDisable,
ENUM_COUNT // must be the last element in the enum class
};
std::unordered_set<ModifierKeyState> FromVkKeyScan(const short vkKeyScanFlags);
std::unordered_set<ModifierKeyState> FromConsoleControlKeyFlags(const DWORD flags);
DWORD ToConsoleControlKeyFlag(const ModifierKeyState modifierKey) noexcept;
class KeyEvent : public IInputEvent
{
public:
enum class Modifiers : DWORD
{
None = 0,
RightAlt = RIGHT_ALT_PRESSED,
LeftAlt = LEFT_ALT_PRESSED,
RightCtrl = RIGHT_CTRL_PRESSED,
LeftCtrl = LEFT_CTRL_PRESSED,
Shift = SHIFT_PRESSED,
NumLock = NUMLOCK_ON,
ScrollLock = SCROLLLOCK_ON,
CapsLock = CAPSLOCK_ON,
EnhancedKey = ENHANCED_KEY,
DbcsChar = NLS_DBCSCHAR,
Alphanumeric = NLS_ALPHANUMERIC,
Katakana = NLS_KATAKANA,
Hiragana = NLS_HIRAGANA,
Roman = NLS_ROMAN,
ImeConvert = NLS_IME_CONVERSION,
AltNumpad = ALTNUMPAD_BIT,
ImeDisable = NLS_IME_DISABLE
return INPUT_RECORD{
.EventType = KEY_EVENT,
.Event = {
.KeyEvent = {
.bKeyDown = bKeyDown,
.wRepeatCount = wRepeatCount,
.wVirtualKeyCode = wVirtualKeyCode,
.wVirtualScanCode = wVirtualScanCode,
.uChar = { .UnicodeChar = UnicodeChar },
.dwControlKeyState = dwControlKeyState,
},
},
};
constexpr KeyEvent(const KEY_EVENT_RECORD& record) :
_keyDown{ !!record.bKeyDown },
_repeatCount{ record.wRepeatCount },
_virtualKeyCode{ record.wVirtualKeyCode },
_virtualScanCode{ record.wVirtualScanCode },
_charData{ record.uChar.UnicodeChar },
_activeModifierKeys{ record.dwControlKeyState }
{
}
constexpr KeyEvent(const bool keyDown,
const WORD repeatCount,
const WORD virtualKeyCode,
const WORD virtualScanCode,
const wchar_t charData,
const DWORD activeModifierKeys) :
_keyDown{ keyDown },
_repeatCount{ repeatCount },
_virtualKeyCode{ virtualKeyCode },
_virtualScanCode{ virtualScanCode },
_charData{ charData },
_activeModifierKeys{ activeModifierKeys }
{
}
constexpr KeyEvent() noexcept :
_keyDown{ 0 },
_repeatCount{ 0 },
_virtualKeyCode{ 0 },
_virtualScanCode{ 0 },
_charData{ 0 },
_activeModifierKeys{ 0 }
{
}
static std::pair<KeyEvent, KeyEvent> MakePair(
const WORD repeatCount,
const WORD virtualKeyCode,
const WORD virtualScanCode,
const wchar_t charData,
const DWORD activeModifierKeys)
{
return std::make_pair<KeyEvent, KeyEvent>(
{ true,
repeatCount,
virtualKeyCode,
virtualScanCode,
charData,
activeModifierKeys },
{ false,
repeatCount,
virtualKeyCode,
virtualScanCode,
charData,
activeModifierKeys });
}
~KeyEvent();
KeyEvent(const KeyEvent&) = default;
KeyEvent(KeyEvent&&) = default;
// For these two operators, there seems to be a bug in the compiler:
// See https://stackoverflow.com/a/60206505/1481137
// > C.128 applies only to virtual member functions, but operator= is not
// > virtual in your base class and neither does it have the same signature as
// > in the derived class, so there is no reason for it to apply.
#pragma warning(push)
#pragma warning(disable : 26456)
KeyEvent& operator=(const KeyEvent&) & = default;
KeyEvent& operator=(KeyEvent&&) & = default;
#pragma warning(pop)
INPUT_RECORD ToInputRecord() const noexcept override;
InputEventType EventType() const noexcept override;
constexpr bool IsShiftPressed() const noexcept
{
return WI_IsFlagSet(GetActiveModifierKeys(), SHIFT_PRESSED);
}
constexpr bool IsAltPressed() const noexcept
{
return WI_IsAnyFlagSet(GetActiveModifierKeys(), ALT_PRESSED);
}
constexpr bool IsCtrlPressed() const noexcept
{
return WI_IsAnyFlagSet(GetActiveModifierKeys(), CTRL_PRESSED);
}
constexpr bool IsAltGrPressed() const noexcept
{
return WI_IsFlagSet(GetActiveModifierKeys(), LEFT_CTRL_PRESSED) &&
WI_IsFlagSet(GetActiveModifierKeys(), RIGHT_ALT_PRESSED);
}
constexpr bool IsModifierPressed() const noexcept
{
return WI_IsAnyFlagSet(GetActiveModifierKeys(), MOD_PRESSED);
}
constexpr bool IsCursorKey() const noexcept
{
// true iff vk in [End, Home, Left, Up, Right, Down]
return (_virtualKeyCode >= VK_END) && (_virtualKeyCode <= VK_DOWN);
}
constexpr bool IsAltNumpadSet() const noexcept
{
return WI_IsFlagSet(GetActiveModifierKeys(), ALTNUMPAD_BIT);
}
constexpr bool IsKeyDown() const noexcept
{
return _keyDown;
}
constexpr bool IsPauseKey() const noexcept
{
return (_virtualKeyCode == VK_PAUSE);
}
constexpr WORD GetRepeatCount() const noexcept
{
return _repeatCount;
}
constexpr WORD GetVirtualKeyCode() const noexcept
{
return _virtualKeyCode;
}
constexpr WORD GetVirtualScanCode() const noexcept
{
return _virtualScanCode;
}
constexpr wchar_t GetCharData() const noexcept
{
return _charData;
}
constexpr DWORD GetActiveModifierKeys() const noexcept
{
return static_cast<DWORD>(_activeModifierKeys);
}
void SetKeyDown(const bool keyDown) noexcept;
void SetRepeatCount(const WORD repeatCount) noexcept;
void SetVirtualKeyCode(const WORD virtualKeyCode) noexcept;
void SetVirtualScanCode(const WORD virtualScanCode) noexcept;
void SetCharData(const char character) noexcept;
void SetCharData(const wchar_t character) noexcept;
void SetActiveModifierKeys(const DWORD activeModifierKeys) noexcept;
void DeactivateModifierKey(const ModifierKeyState modifierKey) noexcept;
void ActivateModifierKey(const ModifierKeyState modifierKey) noexcept;
bool DoActiveModifierKeysMatch(const std::unordered_set<ModifierKeyState>& consoleModifiers) const noexcept;
bool IsCommandLineEditingKey() const noexcept;
bool IsPopupKey() const noexcept;
// Function Description:
// - Returns true if the given VKey represents a modifier key - shift, alt,
// control or the Win key.
// Arguments:
// - vkey: the VKEY to check
// Return Value:
// - true iff the key is a modifier key.
constexpr static bool IsModifierKey(const WORD vkey)
{
return (vkey == VK_CONTROL) ||
(vkey == VK_LCONTROL) ||
(vkey == VK_RCONTROL) ||
(vkey == VK_MENU) ||
(vkey == VK_LMENU) ||
(vkey == VK_RMENU) ||
(vkey == VK_SHIFT) ||
(vkey == VK_LSHIFT) ||
(vkey == VK_RSHIFT) ||
// There is no VK_WIN
(vkey == VK_LWIN) ||
(vkey == VK_RWIN);
};
private:
bool _keyDown;
WORD _repeatCount;
WORD _virtualKeyCode;
WORD _virtualScanCode;
wchar_t _charData;
Modifiers _activeModifierKeys;
friend constexpr bool operator==(const KeyEvent& a, const KeyEvent& b) noexcept;
#ifdef UNIT_TESTING
friend std::wostream& operator<<(std::wostream& stream, const KeyEvent* const pKeyEvent);
#endif
};
constexpr bool operator==(const KeyEvent& a, const KeyEvent& b) noexcept
{
return (a._keyDown == b._keyDown &&
a._repeatCount == b._repeatCount &&
a._virtualKeyCode == b._virtualKeyCode &&
a._virtualScanCode == b._virtualScanCode &&
a._charData == b._charData &&
a._activeModifierKeys == b._activeModifierKeys);
}
#ifdef UNIT_TESTING
std::wostream& operator<<(std::wostream& stream, const KeyEvent* const pKeyEvent);
#endif
class MouseEvent : public IInputEvent
constexpr INPUT_RECORD SynthesizeMouseEvent(til::point dwMousePosition, uint32_t dwButtonState, uint32_t dwControlKeyState, uint32_t dwEventFlags)
{
public:
constexpr MouseEvent(const MOUSE_EVENT_RECORD& record) :
_position{ til::wrap_coord(record.dwMousePosition) },
_buttonState{ record.dwButtonState },
_activeModifierKeys{ record.dwControlKeyState },
_eventFlags{ record.dwEventFlags }
{
}
return INPUT_RECORD{
.EventType = MOUSE_EVENT,
.Event = {
.MouseEvent = {
.dwMousePosition = {
::base::saturated_cast<SHORT>(dwMousePosition.x),
::base::saturated_cast<SHORT>(dwMousePosition.y),
},
.dwButtonState = dwButtonState,
.dwControlKeyState = dwControlKeyState,
.dwEventFlags = dwEventFlags,
},
},
};
}
constexpr MouseEvent(const til::point position,
const DWORD buttonState,
const DWORD activeModifierKeys,
const DWORD eventFlags) :
_position{ position },
_buttonState{ buttonState },
_activeModifierKeys{ activeModifierKeys },
_eventFlags{ eventFlags }
{
}
~MouseEvent();
MouseEvent(const MouseEvent&) = default;
MouseEvent(MouseEvent&&) = default;
MouseEvent& operator=(const MouseEvent&) & = default;
MouseEvent& operator=(MouseEvent&&) & = default;
INPUT_RECORD ToInputRecord() const noexcept override;
InputEventType EventType() const noexcept override;
constexpr bool IsMouseMoveEvent() const noexcept
{
return _eventFlags == MOUSE_MOVED;
}
constexpr til::point GetPosition() const noexcept
{
return _position;
}
constexpr DWORD GetButtonState() const noexcept
{
return _buttonState;
}
constexpr DWORD GetActiveModifierKeys() const noexcept
{
return _activeModifierKeys;
}
constexpr DWORD GetEventFlags() const noexcept
{
return _eventFlags;
}
void SetPosition(const til::point position) noexcept;
void SetButtonState(const DWORD buttonState) noexcept;
void SetActiveModifierKeys(const DWORD activeModifierKeys) noexcept;
void SetEventFlags(const DWORD eventFlags) noexcept;
private:
til::point _position;
DWORD _buttonState;
DWORD _activeModifierKeys;
DWORD _eventFlags;
#ifdef UNIT_TESTING
friend std::wostream& operator<<(std::wostream& stream, const MouseEvent* const pMouseEvent);
#endif
};
#ifdef UNIT_TESTING
std::wostream& operator<<(std::wostream& stream, const MouseEvent* const pMouseEvent);
#endif
class WindowBufferSizeEvent : public IInputEvent
constexpr INPUT_RECORD SynthesizeWindowBufferSizeEvent(til::size dwSize)
{
public:
constexpr WindowBufferSizeEvent(const WINDOW_BUFFER_SIZE_RECORD& record) :
_size{ til::wrap_coord_size(record.dwSize) }
{
}
return INPUT_RECORD{
.EventType = WINDOW_BUFFER_SIZE_EVENT,
.Event = {
.WindowBufferSizeEvent = {
.dwSize = {
::base::saturated_cast<SHORT>(dwSize.width),
::base::saturated_cast<SHORT>(dwSize.height),
},
},
},
};
}
constexpr WindowBufferSizeEvent(const til::size size) :
_size{ size }
{
}
~WindowBufferSizeEvent();
WindowBufferSizeEvent(const WindowBufferSizeEvent&) = default;
WindowBufferSizeEvent(WindowBufferSizeEvent&&) = default;
WindowBufferSizeEvent& operator=(const WindowBufferSizeEvent&) & = default;
WindowBufferSizeEvent& operator=(WindowBufferSizeEvent&&) & = default;
INPUT_RECORD ToInputRecord() const noexcept override;
InputEventType EventType() const noexcept override;
constexpr til::size GetSize() const noexcept
{
return _size;
}
void SetSize(const til::size size) noexcept;
private:
til::size _size;
#ifdef UNIT_TESTING
friend std::wostream& operator<<(std::wostream& stream, const WindowBufferSizeEvent* const pEvent);
#endif
};
#ifdef UNIT_TESTING
std::wostream& operator<<(std::wostream& stream, const WindowBufferSizeEvent* const pEvent);
#endif
class MenuEvent : public IInputEvent
constexpr INPUT_RECORD SynthesizeMenuEvent(uint32_t dwCommandId)
{
public:
constexpr MenuEvent(const MENU_EVENT_RECORD& record) :
_commandId{ record.dwCommandId }
{
}
return INPUT_RECORD{
.EventType = MENU_EVENT,
.Event = {
.MenuEvent = {
.dwCommandId = dwCommandId,
},
},
};
}
constexpr MenuEvent(const UINT commandId) :
_commandId{ commandId }
{
}
~MenuEvent();
MenuEvent(const MenuEvent&) = default;
MenuEvent(MenuEvent&&) = default;
MenuEvent& operator=(const MenuEvent&) & = default;
MenuEvent& operator=(MenuEvent&&) & = default;
INPUT_RECORD ToInputRecord() const noexcept override;
InputEventType EventType() const noexcept override;
constexpr UINT GetCommandId() const noexcept
{
return _commandId;
}
void SetCommandId(const UINT commandId) noexcept;
private:
UINT _commandId;
#ifdef UNIT_TESTING
friend std::wostream& operator<<(std::wostream& stream, const MenuEvent* const pMenuEvent);
#endif
};
#ifdef UNIT_TESTING
std::wostream& operator<<(std::wostream& stream, const MenuEvent* const pMenuEvent);
#endif
class FocusEvent : public IInputEvent
constexpr INPUT_RECORD SynthesizeFocusEvent(bool bSetFocus)
{
public:
constexpr FocusEvent(const FOCUS_EVENT_RECORD& record) :
_focus{ !!record.bSetFocus }
{
}
constexpr FocusEvent(const bool focus) :
_focus{ focus }
{
}
~FocusEvent();
FocusEvent(const FocusEvent&) = default;
FocusEvent(FocusEvent&&) = default;
FocusEvent& operator=(const FocusEvent&) & = default;
FocusEvent& operator=(FocusEvent&&) & = default;
INPUT_RECORD ToInputRecord() const noexcept override;
InputEventType EventType() const noexcept override;
constexpr bool GetFocus() const noexcept
{
return _focus;
}
void SetFocus(const bool focus) noexcept;
private:
bool _focus;
#ifdef UNIT_TESTING
friend std::wostream& operator<<(std::wostream& stream, const FocusEvent* const pFocusEvent);
#endif
};
#ifdef UNIT_TESTING
std::wostream& operator<<(std::wostream& stream, const FocusEvent* const pFocusEvent);
#endif
return INPUT_RECORD{
.EventType = FOCUS_EVENT,
.Event = {
.FocusEvent = {
.bSetFocus = bSetFocus,
},
},
};
}

View File

@ -16,12 +16,6 @@
<ClCompile Include="..\convert.cpp" />
<ClCompile Include="..\colorTable.cpp" />
<ClCompile Include="..\GlyphWidth.cpp" />
<ClCompile Include="..\MouseEvent.cpp" />
<ClCompile Include="..\FocusEvent.cpp" />
<ClCompile Include="..\IInputEvent.cpp" />
<ClCompile Include="..\KeyEvent.cpp" />
<ClCompile Include="..\MenuEvent.cpp" />
<ClCompile Include="..\ModifierKeyState.cpp" />
<ClCompile Include="..\ScreenInfoUiaProviderBase.cpp" />
<ClCompile Include="..\sgrStack.cpp" />
<ClCompile Include="..\ThemeUtils.cpp" />
@ -30,7 +24,6 @@
<ClCompile Include="..\TermControlUiaTextRange.cpp" />
<ClCompile Include="..\TermControlUiaProvider.cpp" />
<ClCompile Include="..\Viewport.cpp" />
<ClCompile Include="..\WindowBufferSizeEvent.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@ -66,4 +59,4 @@
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
</Project>
</Project>

View File

@ -24,30 +24,9 @@
<ClCompile Include="..\colorTable.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\MouseEvent.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\FocusEvent.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\IInputEvent.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\KeyEvent.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\MenuEvent.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\ModifierKeyState.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Viewport.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\WindowBufferSizeEvent.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\precomp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -69,9 +48,6 @@
<ClCompile Include="..\ThemeUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Environment.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\sgrStack.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -116,24 +92,6 @@
<ClInclude Include="..\UiaTextRangeBase.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\CodepointWidthDetector.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\convert.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\GlyphWidth.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\IInputEvent.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\Viewport.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\precomp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\ScreenInfoUiaProviderBase.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -152,9 +110,6 @@
<ClInclude Include="..\inc\ThemeUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\Environment.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\sgrStack.hpp">
<Filter>Header Files</Filter>
</ClInclude>

View File

@ -30,15 +30,9 @@ PRECOMPILED_INCLUDE = ..\precomp.h
SOURCES= \
..\CodepointWidthDetector.cpp \
..\ColorFix.cpp \
..\IInputEvent.cpp \
..\FocusEvent.cpp \
..\GlyphWidth.cpp \
..\KeyEvent.cpp \
..\MenuEvent.cpp \
..\ModifierKeyState.cpp \
..\MouseEvent.cpp \
..\Viewport.cpp \
..\WindowBufferSizeEvent.cpp \
..\convert.cpp \
..\colorTable.cpp \
..\utils.cpp \

View File

@ -45,9 +45,28 @@
</Expand>
</Type>
<Type Name="KeyEvent">
<DisplayString Condition="_keyDown">{{↓ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}}</DisplayString>
<DisplayString Condition="!_keyDown">{{↑ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}}</DisplayString>
<Type Name="_KEY_EVENT_RECORD">
<DisplayString Condition="bKeyDown != 0">↓ rep:{wRepeatCount} vk:{wVirtualKeyCode} sc:{wVirtualScanCode} ch:{uChar.UnicodeChar} ctrl:{(unsigned short)dwControlKeyState,x}</DisplayString>
<DisplayString Condition="bKeyDown == 0">↑ rep:{wRepeatCount} vk:{wVirtualKeyCode} sc:{wVirtualScanCode} ch:{uChar.UnicodeChar} ctrl:{(unsigned short)dwControlKeyState,x}</DisplayString>
</Type>
<Type Name="_MOUSE_EVENT_RECORD">
<DisplayString>pos:{dwMousePosition.X},{dwMousePosition.Y} state:{dwButtonState,x} ctrl:{(unsigned short)dwControlKeyState,x} flags:{(unsigned short)dwEventFlags,x}</DisplayString>
</Type>
<Type Name="_WINDOW_BUFFER_SIZE_RECORD">
<DisplayString>size:{dwSize.X},{dwSize.Y}</DisplayString>
</Type>
<Type Name="_MENU_EVENT_RECORD">
<DisplayString>id:{dwCommandId}</DisplayString>
</Type>
<Type Name="_FOCUS_EVENT_RECORD">
<DisplayString>focus:{bSetFocus}</DisplayString>
</Type>
<Type Name="_INPUT_RECORD">
<DisplayString Condition="EventType == 0x0001">Key {Event.KeyEvent}</DisplayString>
<DisplayString Condition="EventType == 0x0002">Mouse {Event.MouseEvent}</DisplayString>
<DisplayString Condition="EventType == 0x0004">WindowBufferSize {Event.WindowBufferSizeEvent}</DisplayString>
<DisplayString Condition="EventType == 0x0008">Menu {Event.MenuEvent}</DisplayString>
<DisplayString Condition="EventType == 0x0010">Focus {Event.FocusEvent}</DisplayString>
</Type>
<Type Name="til::size">