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 // modifier key. We'll wait for a real keystroke to dismiss the
// GH #7395 - don't update selection when taking PrintScreen // GH #7395 - don't update selection when taking PrintScreen
// selection. // selection.
return HasSelection() && !KeyEvent::IsModifierKey(vkey) && vkey != VK_SNAPSHOT; return HasSelection() && ::Microsoft::Terminal::Core::Terminal::IsInputKey(vkey);
} }
bool ControlCore::TryMarkModeKeybinding(const WORD 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. // 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 // GH#6481 - Additionally, make sure the key was actually pressed. This
// check will make sure we behave the same as before GH#6309 // check will make sure we behave the same as before GH#6309
if (!KeyEvent::IsModifierKey(vkey) && keyDown) if (IsInputKey(vkey) && keyDown)
{ {
TrySnapOnInput(); TrySnapOnInput();
} }
@ -714,8 +714,8 @@ bool Terminal::SendKeyEvent(const WORD vkey,
return false; return false;
} }
const KeyEvent keyEv{ keyDown, 1, vkey, sc, ch, states.Value() }; const auto keyEv = SynthesizeKeyEvent(keyDown, 1, vkey, sc, ch, states.Value());
return _handleTerminalInputResult(_terminalInput.HandleKey(&keyEv)); return _handleTerminalInputResult(_terminalInput.HandleKey(keyEv));
} }
// Method Description: // Method Description:
@ -791,8 +791,8 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro
MarkOutputStart(); MarkOutputStart();
} }
const KeyEvent keyDown{ true, 1, vkey, scanCode, ch, states.Value() }; const auto keyDown = SynthesizeKeyEvent(true, 1, vkey, scanCode, ch, states.Value());
return _handleTerminalInputResult(_terminalInput.HandleKey(&keyDown)); return _handleTerminalInputResult(_terminalInput.HandleKey(keyDown));
} }
// Method Description: // Method Description:

View File

@ -60,6 +60,22 @@ class Microsoft::Terminal::Core::Terminal final :
using RenderSettings = Microsoft::Console::Render::RenderSettings; using RenderSettings = Microsoft::Console::Render::RenderSettings;
public: 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(); Terminal();
void Create(til::size viewportSize, 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 wchar_t wch);
bool IsWordDelim(const std::wstring_view charData); 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); 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); const auto dwControlKeyState = GetControlKeyState(0);
std::deque<std::unique_ptr<IInputEvent>> inEvents; InputEventQueue inEvents;
KeyEvent keyEvent{ TRUE, // keydown auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, 0, dwControlKeyState);
1, // repeatCount
0, // virtualKeyCode
0, // virtualScanCode
0, // charData
dwControlKeyState }; // activeModifierKeys
for (const auto& ch : text) for (const auto& ch : text)
{ {
keyEvent.SetCharData(ch); keyEvent.Event.KeyEvent.uChar.UnicodeChar = ch;
inEvents.push_back(std::make_unique<KeyEvent>(keyEvent)); inEvents.push_back(keyEvent);
} }
gci.pInputBuffer->Write(inEvents); gci.pInputBuffer->Write(inEvents);

View File

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

View File

@ -1919,11 +1919,11 @@ void DbcsTests::TestMultibyteInputCoalescing()
DWORD count; 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)); 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)); 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_WIN32_BOOL_SUCCEEDED(ReadConsoleInputW(in, &actual[0], 2, &count));
VERIFY_ARE_EQUAL(1u, 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]); VERIFY_ARE_EQUAL(expected, actual[0]);
} }

View File

@ -86,7 +86,6 @@ class KeyPressTests
expectedRecord.Event.KeyEvent.uChar.UnicodeChar = 0x0; expectedRecord.Event.KeyEvent.uChar.UnicodeChar = 0x0;
expectedRecord.Event.KeyEvent.bKeyDown = true; expectedRecord.Event.KeyEvent.bKeyDown = true;
expectedRecord.Event.KeyEvent.dwControlKeyState = ENHANCED_KEY; 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.wRepeatCount = SINGLE_KEY_REPEAT;
expectedRecord.Event.KeyEvent.wVirtualKeyCode = VK_APPS; expectedRecord.Event.KeyEvent.wVirtualKeyCode = VK_APPS;
expectedRecord.Event.KeyEvent.wVirtualScanCode = (WORD)scanCode; expectedRecord.Event.KeyEvent.wVirtualScanCode = (WORD)scanCode;

View File

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

View File

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

View File

@ -183,7 +183,7 @@ size_t InputBuffer::PeekCached(bool isUnicode, size_t count, InputEventQueue& ta
break; break;
} }
target.push_back(IInputEvent::Create(e->ToInputRecord())); target.push_back(e);
i++; i++;
} }
@ -222,7 +222,7 @@ void InputBuffer::_switchReadingModeSlowPath(ReadingMode mode)
_cachedTextW = std::wstring{}; _cachedTextW = std::wstring{};
_cachedTextReaderW = {}; _cachedTextReaderW = {};
_cachedInputEvents = std::deque<std::unique_ptr<IInputEvent>>{}; _cachedInputEvents = std::deque<INPUT_RECORD>{};
_readingMode = mode; _readingMode = mode;
} }
@ -234,9 +234,9 @@ void InputBuffer::_switchReadingModeSlowPath(ReadingMode mode)
// - None // - None
// Return Value: // Return Value:
// - true if partial char data is available, false otherwise // - 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: // Routine Description:
@ -245,23 +245,10 @@ bool InputBuffer::IsWritePartialByteSequenceAvailable()
// - peek - if true, data will not be removed after being fetched // - peek - if true, data will not be removed after being fetched
// Return Value: // Return Value:
// - the partial char data. may be nullptr if no data is available // - 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()) _writePartialByteSequenceAvailable = false;
{ return _writePartialByteSequence;
return std::unique_ptr<IInputEvent>();
}
if (peek)
{
return IInputEvent::Create(_writePartialByteSequence->ToInputRecord());
}
else
{
std::unique_ptr<IInputEvent> outEvent;
outEvent.swap(_writePartialByteSequence);
return outEvent;
}
} }
// Routine Description: // Routine Description:
@ -271,9 +258,10 @@ std::unique_ptr<IInputEvent> InputBuffer::FetchWritePartialByteSequence(_In_ boo
// - event - The event to store // - event - The event to store
// Return Value: // Return Value:
// - None // - 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: // Routine Description:
@ -348,8 +336,8 @@ void InputBuffer::Flush()
// - The console lock must be held when calling this routine. // - The console lock must be held when calling this routine.
void InputBuffer::FlushAllButKeys() void InputBuffer::FlushAllButKeys()
{ {
auto newEnd = std::remove_if(_storage.begin(), _storage.end(), [](const std::unique_ptr<IInputEvent>& event) { auto newEnd = std::remove_if(_storage.begin(), _storage.end(), [](const INPUT_RECORD& event) {
return event->EventType() != InputEventType::KeyEvent; return event.EventType != KEY_EVENT;
}); });
_storage.erase(newEnd, _storage.end()); _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. // - 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) // - 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. // - 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 size_t AmountToRead,
const bool Peek, const bool Peek,
const bool WaitForData, const bool WaitForData,
@ -417,31 +405,29 @@ try
while (it != end && OutEvents.size() < AmountToRead) while (it != end && OutEvents.size() < AmountToRead)
{ {
auto event = IInputEvent::Create((*it)->ToInputRecord()); if (it->EventType == KEY_EVENT)
if (event->EventType() == InputEventType::KeyEvent)
{ {
const auto keyEvent = static_cast<KeyEvent*>(event.get()); auto event = *it;
WORD repeat = 1; WORD repeat = 1;
// for stream reads we need to split any key events that have been coalesced // for stream reads we need to split any key events that have been coalesced
if (Stream) if (Stream)
{ {
repeat = keyEvent->GetRepeatCount(); repeat = std::max<WORD>(1, event.Event.KeyEvent.wRepeatCount);
keyEvent->SetRepeatCount(1); event.Event.KeyEvent.wRepeatCount = 1;
} }
if (Unicode) if (Unicode)
{ {
do do
{ {
OutEvents.push_back(std::make_unique<KeyEvent>(*keyEvent)); OutEvents.push_back(event);
repeat--; repeat--;
} while (repeat > 0 && OutEvents.size() < AmountToRead); } while (repeat > 0 && OutEvents.size() < AmountToRead);
} }
else else
{ {
const auto wch = keyEvent->GetCharData(); const auto wch = event.Event.KeyEvent.uChar.UnicodeChar;
char buffer[8]; char buffer[8];
const auto length = WideCharToMultiByte(cp, 0, &wch, 1, &buffer[0], sizeof(buffer), nullptr, nullptr); const auto length = WideCharToMultiByte(cp, 0, &wch, 1, &buffer[0], sizeof(buffer), nullptr, nullptr);
@ -453,9 +439,10 @@ try
{ {
for (const auto& ch : str) for (const auto& ch : str)
{ {
auto tempEvent = std::make_unique<KeyEvent>(*keyEvent); // char is signed and assigning it to UnicodeChar would cause sign-extension.
tempEvent->SetCharData(ch); // unsigned char doesn't have this problem.
OutEvents.push_back(std::move(tempEvent)); event.Event.KeyEvent.uChar.UnicodeChar = til::bit_cast<uint8_t>(ch);
OutEvents.push_back(event);
} }
repeat--; repeat--;
} while (repeat > 0 && OutEvents.size() < AmountToRead); } while (repeat > 0 && OutEvents.size() < AmountToRead);
@ -463,14 +450,13 @@ try
if (repeat && !Peek) if (repeat && !Peek)
{ {
const auto originalKeyEvent = static_cast<KeyEvent*>((*it).get()); it->Event.KeyEvent.wRepeatCount = repeat;
originalKeyEvent->SetRepeatCount(repeat);
break; break;
} }
} }
else else
{ {
OutEvents.push_back(std::move(event)); OutEvents.push_back(*it);
} }
++it; ++it;
@ -498,51 +484,6 @@ catch (...)
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException()); 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: // Routine Description:
// - Writes events to the beginning of the input buffer. // - Writes events to the beginning of the input buffer.
// Arguments: // Arguments:
@ -552,7 +493,7 @@ catch (...)
// S_OK if successful. // S_OK if successful.
// Note: // Note:
// - The console lock must be held when calling this routine. // - 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 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. // this way to handle any coalescing that might occur.
// get all of the existing records, "emptying" the buffer // get all of the existing records, "emptying" the buffer
std::deque<std::unique_ptr<IInputEvent>> existingStorage; std::deque<INPUT_RECORD> existingStorage;
existingStorage.swap(_storage); existingStorage.swap(_storage);
// We will need this variable to pass to _WriteBuffer so it can attempt to determine wait status. // 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); _WriteBuffer(inEvents, prependEventsWritten, unusedWaitStatus);
FAIL_FAST_IF(!(unusedWaitStatus)); FAIL_FAST_IF(!(unusedWaitStatus));
// write all previously existing records for (const auto& event : existingStorage)
size_t existingEventsWritten; {
_WriteBuffer(existingStorage, existingEventsWritten, unusedWaitStatus); _storage.push_back(event);
FAIL_FAST_IF(!(!unusedWaitStatus)); }
// We need to set the wait event if there were 0 events in the // We need to set the wait event if there were 0 events in the
// input queue when we started. // 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. // - The console lock must be held when calling this routine.
// - any outside references to inEvent will ben invalidated after // - any outside references to inEvent will ben invalidated after
// calling this method. // calling this method.
size_t InputBuffer::Write(_Inout_ std::unique_ptr<IInputEvent> inEvent) size_t InputBuffer::Write(const INPUT_RECORD& inEvent)
{ {
try return Write(std::span{ &inEvent, 1 });
{
std::deque<std::unique_ptr<IInputEvent>> inEvents;
inEvents.push_back(std::move(inEvent));
return Write(inEvents);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return 0;
}
} }
// Routine Description: // 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. // - The number of events that were written to input buffer.
// Note: // Note:
// - The console lock must be held when calling this routine. // - 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 try
{ {
@ -694,7 +625,7 @@ void InputBuffer::WriteFocusEvent(bool focused) noexcept
{ {
// This is a mini-version of Write(). // This is a mini-version of Write().
const auto wasEmpty = _storage.empty(); const auto wasEmpty = _storage.empty();
_storage.push_back(std::make_unique<FocusEvent>(focused)); _storage.push_back(SynthesizeFocusEvent(focused));
if (wasEmpty) if (wasEmpty)
{ {
ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
@ -760,9 +691,7 @@ static bool IsPauseKey(const KEY_EVENT_RECORD& event)
// Note: // Note:
// - The console lock must be held when calling this routine. // - The console lock must be held when calling this routine.
// - will throw on failure // - will throw on failure
void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents, void InputBuffer::_WriteBuffer(const std::span<const INPUT_RECORD>& inEvents, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent)
_Out_ size_t& eventsWritten,
_Out_ bool& setWaitEvent)
{ {
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); 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 initialInEventsSize = inEvents.size();
const auto vtInputMode = IsInVirtualTerminalInputMode(); 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 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); UnblockWriteConsole(CONSOLE_OUTPUT_SUSPENDED);
continue; continue;
} }
// intercept control-s // 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); WI_SetFlag(gci.Flags, CONSOLE_SUSPENDED);
continue; continue;
@ -797,7 +726,7 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
if (vtInputMode) if (vtInputMode)
{ {
// GH#11682: TerminalInput::HandleKey can handle both KeyEvents and Focus events seamlessly // 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); _HandleTerminalInputCallback(*out);
eventsWritten++; 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. // At this point, the event was neither coalesced, nor processed by VT.
_storage.push_back(std::move(inEvent)); _storage.push_back(inEvent);
++eventsWritten; ++eventsWritten;
} }
if (initiallyEmptyQueue && !_storage.empty()) 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 // the buffer with updated values from an incoming event, instead of
// storing the incoming event (which would make the original one // storing the incoming event (which would make the original one
// redundant/out of date with the most current state). // 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(); 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()); const auto& inMouse = inEvent.Event.MouseEvent;
auto& lastMouse = *static_cast<MouseEvent*>(lastEvent.get()); 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; 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()); const auto& inKey = inEvent.Event.KeyEvent;
auto& lastKey = *static_cast<KeyEvent*>(lastEvent.get()); auto& lastKey = lastEvent.Event.KeyEvent;
if (lastKey.IsKeyDown() && inKey.IsKeyDown() && if (lastKey.bKeyDown && inKey.bKeyDown &&
(lastKey.GetVirtualScanCode() == inKey.GetVirtualScanCode() || WI_IsFlagSet(inKey.GetActiveModifierKeys(), NLS_IME_CONVERSION)) && (lastKey.wVirtualScanCode == inKey.wVirtualScanCode || WI_IsFlagSet(inKey.dwControlKeyState, NLS_IME_CONVERSION)) &&
lastKey.GetCharData() == inKey.GetCharData() && lastKey.uChar.UnicodeChar == inKey.uChar.UnicodeChar &&
lastKey.GetActiveModifierKeys() == inKey.GetActiveModifierKeys() && lastKey.dwControlKeyState == inKey.dwControlKeyState &&
// TODO: This behavior is an import from old conhost v1 and has been broken for decades. // 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 // 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. // 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). // 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 // 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. // 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; return true;
} }
} }
@ -908,7 +837,7 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType&
for (const auto& wch : text) 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) if (!_vtInputShouldSuppress)

View File

@ -1,20 +1,5 @@
/*++ // Copyright (c) Microsoft Corporation.
Copyright (c) Microsoft Corporation // Licensed under the MIT license.
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)
--*/
#pragma once #pragma once
@ -52,9 +37,9 @@ public:
void Cache(bool isUnicode, InputEventQueue& source, size_t expectedSourceSize); void Cache(bool isUnicode, InputEventQueue& source, size_t expectedSourceSize);
// storage API for partial dbcs bytes being written to the buffer // storage API for partial dbcs bytes being written to the buffer
bool IsWritePartialByteSequenceAvailable(); bool IsWritePartialByteSequenceAvailable() const noexcept;
std::unique_ptr<IInputEvent> FetchWritePartialByteSequence(_In_ bool peek); const INPUT_RECORD& FetchWritePartialByteSequence() noexcept;
void StoreWritePartialByteSequence(std::unique_ptr<IInputEvent> event); void StoreWritePartialByteSequence(const INPUT_RECORD& event) noexcept;
void ReinitializeInputBuffer(); void ReinitializeInputBuffer();
void WakeUpReadersWaitingForData(); void WakeUpReadersWaitingForData();
@ -63,24 +48,16 @@ public:
void Flush(); void Flush();
void FlushAllButKeys(); void FlushAllButKeys();
[[nodiscard]] NTSTATUS Read(_Out_ std::deque<std::unique_ptr<IInputEvent>>& OutEvents, [[nodiscard]] NTSTATUS Read(_Out_ InputEventQueue& OutEvents,
const size_t AmountToRead, const size_t AmountToRead,
const bool Peek, const bool Peek,
const bool WaitForData, const bool WaitForData,
const bool Unicode, const bool Unicode,
const bool Stream); const bool Stream);
[[nodiscard]] NTSTATUS Read(_Out_ std::unique_ptr<IInputEvent>& inEvent, size_t Prepend(const std::span<const INPUT_RECORD>& inEvents);
const bool Peek, size_t Write(const INPUT_RECORD& inEvent);
const bool WaitForData, size_t Write(const std::span<const INPUT_RECORD>& inEvents);
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);
void WriteFocusEvent(bool focused) noexcept; void WriteFocusEvent(bool focused) noexcept;
bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta); bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta);
@ -102,11 +79,12 @@ private:
std::string_view _cachedTextReaderA; std::string_view _cachedTextReaderA;
std::wstring _cachedTextW; std::wstring _cachedTextW;
std::wstring_view _cachedTextReaderW; std::wstring_view _cachedTextReaderW;
std::deque<std::unique_ptr<IInputEvent>> _cachedInputEvents; std::deque<INPUT_RECORD> _cachedInputEvents;
ReadingMode _readingMode = ReadingMode::StringA; ReadingMode _readingMode = ReadingMode::StringA;
std::deque<std::unique_ptr<IInputEvent>> _storage; std::deque<INPUT_RECORD> _storage;
std::unique_ptr<IInputEvent> _writePartialByteSequence; INPUT_RECORD _writePartialByteSequence{};
bool _writePartialByteSequenceAvailable = false;
Microsoft::Console::VirtualTerminal::TerminalInput _termInput; Microsoft::Console::VirtualTerminal::TerminalInput _termInput;
Microsoft::Console::Render::VtEngine* _pTtyConnection; Microsoft::Console::Render::VtEngine* _pTtyConnection;
@ -118,12 +96,8 @@ private:
void _switchReadingMode(ReadingMode mode); void _switchReadingMode(ReadingMode mode);
void _switchReadingModeSlowPath(ReadingMode mode); void _switchReadingModeSlowPath(ReadingMode mode);
void _WriteBuffer(const std::span<const INPUT_RECORD>& inRecords, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent);
void _WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inRecords, bool _CoalesceEvent(const INPUT_RECORD& inEvent) noexcept;
_Out_ size_t& eventsWritten,
_Out_ bool& setWaitEvent);
bool _CoalesceEvent(const std::unique_ptr<IInputEvent>& inEvent) const noexcept;
void _HandleTerminalInputCallback(const Microsoft::Console::VirtualTerminal::TerminalInput::StringType& text); void _HandleTerminalInputCallback(const Microsoft::Console::VirtualTerminal::TerminalInput::StringType& text);
#ifdef UNIT_TESTING #ifdef UNIT_TESTING

View File

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

View File

@ -33,18 +33,17 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) :
// - <none> // - <none>
void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response) 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 // generate a paired key down and key up event for every
// character to be sent into the console's input buffer // character to be sent into the console's input buffer
for (const auto& wch : response) for (const auto& wch : response)
{ {
// This wasn't from a real keyboard, so we're leaving key/scan codes blank. // This wasn't from a real keyboard, so we're leaving key/scan codes blank.
KeyEvent keyEvent{ TRUE, 1, 0, 0, wch, 0 }; auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, wch, 0);
inEvents.push_back(keyEvent);
inEvents.push_back(std::make_unique<KeyEvent>(keyEvent)); keyEvent.Event.KeyEvent.bKeyDown = false;
keyEvent.SetKeyDown(false); inEvents.push_back(keyEvent);
inEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
} }
// TODO GH#4954 During the input refactor we may want to add a "priority" input list // 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 // - pControlKeyState - For certain types of reads, this specifies
// which modifier keys were held. // which modifier keys were held.
// - pOutputData - a pointer to a // - 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 // input events back to the server
// Return Value: // Return Value:
// - true if the wait is done and result buffer/status code can be sent back to the client. // - 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; *pControlKeyState = 0;
*pNumBytes = 0; *pNumBytes = 0;
std::deque<std::unique_ptr<IInputEvent>> readEvents;
// If ctrl-c or ctrl-break was seen, ignore it. // If ctrl-c or ctrl-break was seen, ignore it.
if (WI_IsAnyFlagSet(TerminationReason, (WaitTerminationReason::CtrlC | WaitTerminationReason::CtrlBreak))) if (WI_IsAnyFlagSet(TerminationReason, (WaitTerminationReason::CtrlC | WaitTerminationReason::CtrlBreak)))
{ {
@ -119,7 +117,7 @@ try
} }
// move events to pOutputData // 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); *pNumBytes = _outEvents.size() * sizeof(INPUT_RECORD);
*pOutputDeque = std::move(_outEvents); *pOutputDeque = std::move(_outEvents);

View File

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

View File

@ -16,10 +16,95 @@
#include "../interactivity/inc/ServiceLocator.hpp" #include "../interactivity/inc/ServiceLocator.hpp"
#pragma hdrstop
using Microsoft::Console::Interactivity::ServiceLocator; 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: // Routine Description:
// - This routine is used in stream input. It gets input and filters it for unicode characters. // - This routine is used in stream input. It gets input and filters it for unicode characters.
// Arguments: // Arguments:
@ -56,57 +141,50 @@ using Microsoft::Console::Interactivity::ServiceLocator;
*pdwKeyState = 0; *pdwKeyState = 0;
} }
NTSTATUS Status;
for (;;) for (;;)
{ {
std::unique_ptr<IInputEvent> inputEvent; InputEventQueue events;
Status = pInputBuffer->Read(inputEvent, const auto Status = pInputBuffer->Read(events, 1, false, Wait, true, true);
false, // peek
Wait,
true, // unicode
true); // stream
if (FAILED_NTSTATUS(Status)) if (FAILED_NTSTATUS(Status))
{ {
return Status; return Status;
} }
else if (inputEvent.get() == nullptr) if (events.empty())
{ {
FAIL_FAST_IF(Wait); assert(!Wait);
return STATUS_UNSUCCESSFUL; 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; auto commandLineEditKey = false;
if (pCommandLineEditingKeys) if (pCommandLineEditingKeys)
{ {
commandLineEditKey = keyEvent->IsCommandLineEditingKey(); commandLineEditKey = IsCommandLineEditingKey(Event.Event.KeyEvent);
} }
else if (pPopupKeys) else if (pPopupKeys)
{ {
commandLineEditKey = keyEvent->IsPopupKey(); commandLineEditKey = IsCommandLinePopupKey(Event.Event.KeyEvent);
} }
if (pdwKeyState) 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 // 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] = { const char chT[2] = {
static_cast<char>(HIBYTE(keyEvent->GetCharData())), static_cast<char>(HIBYTE(Event.Event.KeyEvent.uChar.UnicodeChar)),
static_cast<char>(LOBYTE(keyEvent->GetCharData())), static_cast<char>(LOBYTE(Event.Event.KeyEvent.uChar.UnicodeChar)),
}; };
*pwchOut = CharToWchar(chT, 2); *pwchOut = CharToWchar(chT, 2);
} }
@ -115,64 +193,54 @@ using Microsoft::Console::Interactivity::ServiceLocator;
// Because USER doesn't know our codepage, // Because USER doesn't know our codepage,
// it gives us the raw OEM char and we // it gives us the raw OEM char and we
// convert it to a Unicode character. // convert it to a Unicode character.
char chT = LOBYTE(keyEvent->GetCharData()); char chT = LOBYTE(Event.Event.KeyEvent.uChar.UnicodeChar);
*pwchOut = CharToWchar(&chT, 1); *pwchOut = CharToWchar(&chT, 1);
} }
} }
else else
{ {
*pwchOut = keyEvent->GetCharData(); *pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
} }
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
// Ignore Escape and Newline chars // Ignore Escape and Newline chars
else if (keyEvent->IsKeyDown() && if (Event.Event.KeyEvent.bKeyDown &&
(WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT) || (WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT) ||
(keyEvent->GetVirtualKeyCode() != VK_ESCAPE && (Event.Event.KeyEvent.wVirtualKeyCode != VK_ESCAPE &&
keyEvent->GetCharData() != UNICODE_LINEFEED))) Event.Event.KeyEvent.uChar.UnicodeChar != UNICODE_LINEFEED)))
{ {
*pwchOut = keyEvent->GetCharData(); *pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
} }
if (keyEvent->IsKeyDown()) if (Event.Event.KeyEvent.bKeyDown)
{ {
if (pCommandLineEditingKeys && commandLineEditKey) if (pCommandLineEditingKeys && commandLineEditKey)
{ {
*pCommandLineEditingKeys = true; *pCommandLineEditingKeys = true;
*pwchOut = static_cast<wchar_t>(keyEvent->GetVirtualKeyCode()); *pwchOut = static_cast<wchar_t>(Event.Event.KeyEvent.wVirtualKeyCode);
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
else if (pPopupKeys && commandLineEditKey)
if (pPopupKeys && commandLineEditKey)
{ {
*pPopupKeys = true; *pPopupKeys = true;
*pwchOut = static_cast<char>(keyEvent->GetVirtualKeyCode()); *pwchOut = static_cast<char>(Event.Event.KeyEvent.wVirtualKeyCode);
return STATUS_SUCCESS; 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); // This really is the character 0x0000
const auto zeroVKey = LOBYTE(zeroVkeyData); *pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
const auto zeroControlKeyState = HIBYTE(zeroVkeyData); return STATUS_SUCCESS;
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());
}
} }
} }
} }

View File

@ -153,106 +153,45 @@ class ClipboardTests
VERIFY_IS_NOT_NULL(ptr); VERIFY_IS_NOT_NULL(ptr);
} }
TEST_METHOD(CanConvertTextToInputEvents) TEST_METHOD(CanConvertText)
{ {
std::wstring wstr = L"hello world"; static constexpr std::wstring_view input{ L"HeLlO WoRlD" };
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(), const auto events = Clipboard::Instance().TextToKeyEvents(input.data(), input.size());
wstr.size());
VERIFY_ARE_EQUAL(wstr.size() * 2, events.size()); const auto shiftSC = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_SHIFT, MAPVK_VK_TO_VSC));
for (auto wch : wstr) 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 }; const auto state = OneCoreSafeVkKeyScanW(wch);
for (auto isKeyDown : keydownPattern) 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()); expectedEvents.push_back(shiftDown);
std::unique_ptr<KeyEvent> keyEvent; }
keyEvent.reset(static_cast<KeyEvent* const>(events.front().release()));
events.pop_front();
const auto keyState = OneCoreSafeVkKeyScanW(wch); expectedEvents.push_back(event);
VERIFY_ARE_NOT_EQUAL(-1, keyState); event.Event.KeyEvent.bKeyDown = FALSE;
const auto virtualScanCode = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(LOBYTE(keyState), MAPVK_VK_TO_VSC)); expectedEvents.push_back(event);
VERIFY_ARE_EQUAL(wch, keyEvent->GetCharData()); if (shift)
VERIFY_ARE_EQUAL(isKeyDown, keyEvent->IsKeyDown()); {
VERIFY_ARE_EQUAL(1, keyEvent->GetRepeatCount()); expectedEvents.push_back(shiftUp);
VERIFY_ARE_EQUAL(static_cast<DWORD>(0), keyEvent->GetActiveModifierKeys());
VERIFY_ARE_EQUAL(virtualScanCode, keyEvent->GetVirtualScanCode());
VERIFY_ARE_EQUAL(LOBYTE(keyState), keyEvent->GetVirtualKeyCode());
} }
} }
}
TEST_METHOD(CanConvertUppercaseText) VERIFY_ARE_EQUAL(expectedEvents.size(), events.size());
{
std::wstring wstr = L"HeLlO WoRlD"; for (size_t i = 0; i < events.size(); ++i)
size_t uppercaseCount = 0;
for (auto wch : wstr)
{ {
std::isupper(wch) ? ++uppercaseCount : 0; VERIFY_ARE_EQUAL(expectedEvents[i], events[i]);
}
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);
}
}
} }
} }
@ -274,23 +213,22 @@ class ClipboardTests
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(), auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
wstr.size()); wstr.size());
std::deque<KeyEvent> expectedEvents; InputEventQueue expectedEvents;
// should be converted to: // should be converted to:
// 1. AltGr keydown // 1. AltGr keydown
// 2. € keydown // 2. € keydown
// 3. € keyup // 3. € keyup
// 4. AltGr 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(SynthesizeKeyEvent(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(SynthesizeKeyEvent(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(SynthesizeKeyEvent(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(false, 1, VK_MENU, altScanCode, L'\0', ENHANCED_KEY));
VERIFY_ARE_EQUAL(expectedEvents.size(), events.size()); VERIFY_ARE_EQUAL(expectedEvents.size(), events.size());
for (size_t i = 0; i < events.size(); ++i) 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], events[i]);
VERIFY_ARE_EQUAL(expectedEvents[i], currentKeyEvent, NoThrowString().Format(L"i == %d", i));
} }
} }
@ -302,7 +240,7 @@ class ClipboardTests
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(), auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
wstr.size()); wstr.size());
std::deque<KeyEvent> expectedEvents; InputEventQueue expectedEvents;
if constexpr (Feature_UseNumpadEventsForClipboardInput::IsEnabled()) if constexpr (Feature_UseNumpadEventsForClipboardInput::IsEnabled())
{ {
// Inside Windows, where numpad events are enabled, this generated numpad events. // Inside Windows, where numpad events are enabled, this generated numpad events.
@ -313,26 +251,25 @@ class ClipboardTests
// 4. 2nd numpad keydown // 4. 2nd numpad keydown
// 5. 2nd numpad keyup // 5. 2nd numpad keyup
// 6. left alt keyup // 6. left alt keyup
expectedEvents.push_back({ TRUE, 1, VK_MENU, altScanCode, L'\0', LEFT_ALT_PRESSED }); expectedEvents.push_back(SynthesizeKeyEvent(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(SynthesizeKeyEvent(true, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED));
expectedEvents.push_back({ FALSE, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED }); expectedEvents.push_back(SynthesizeKeyEvent(false, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED));
expectedEvents.push_back({ TRUE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED }); expectedEvents.push_back(SynthesizeKeyEvent(true, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED));
expectedEvents.push_back({ FALSE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED }); expectedEvents.push_back(SynthesizeKeyEvent(false, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED));
expectedEvents.push_back({ FALSE, 1, VK_MENU, altScanCode, wstr[0], 0 }); expectedEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, wstr[0], 0));
} }
else else
{ {
// Outside Windows, without numpad events, we just emit the key with a nonzero UnicodeChar // 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(SynthesizeKeyEvent(true, 1, 0, 0, wstr[0], 0));
expectedEvents.push_back({ FALSE, 1, 0, 0, wstr[0], 0 }); expectedEvents.push_back(SynthesizeKeyEvent(false, 1, 0, 0, wstr[0], 0));
} }
VERIFY_ARE_EQUAL(expectedEvents.size(), events.size()); VERIFY_ARE_EQUAL(expectedEvents.size(), events.size());
for (size_t i = 0; i < events.size(); ++i) 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], events[i]);
VERIFY_ARE_EQUAL(expectedEvents[i], currentKeyEvent, NoThrowString().Format(L"i == %d", i));
} }
} }
}; };

View File

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

View File

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

View File

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

View File

@ -278,6 +278,12 @@ namespace til
return tmp; 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 constexpr small_vector_iterator& operator-=(const difference_type off) noexcept
{ {
base::operator-=(off); base::operator-=(off);

View File

@ -3,10 +3,6 @@
#include "precomp.h" #include "precomp.h"
#include "../inc/EventSynthesis.hpp" #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 // TODO: MSFT 14150722 - can these const values be generated at
// runtime without breaking compatibility? // runtime without breaking compatibility?
@ -16,35 +12,29 @@ static constexpr WORD leftShiftScanCode = 0x2A;
// Routine Description: // Routine Description:
// - naively determines the width of a UCS2 encoded wchar (with caveats noted above) // - 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 #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 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) (0x2e80 <= wch && wch <= 0x303e) || // From Unicode 9.0, this range is wide (assorted languages)
|| (0x3041 <= wch && wch <= 0x3094) // Hiragana (0x3041 <= wch && wch <= 0x3094) || // Hiragana
|| (0x30a1 <= wch && wch <= 0x30f6) // Katakana (0x30a1 <= wch && wch <= 0x30f6) || // Katakana
|| (0x3105 <= wch && wch <= 0x312c) // Bopomofo (0x3105 <= wch && wch <= 0x312c) || // Bopomofo
|| (0x3131 <= wch && wch <= 0x318e) // Hangul Elements (0x3131 <= wch && wch <= 0x318e) || // Hangul Elements
|| (0x3190 <= wch && wch <= 0x3247) // From Unicode 9.0, this range is wide (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 (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 (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 (0xa960 <= wch && wch <= 0xa97c) || // Wide Hangul Choseong
|| (0xac00 <= wch && wch <= 0xd7a3) // Korean Hangul Syllables (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] (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] (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] (0xfe30 <= wch && wch <= 0xfe6b) || // From Unicode 9.0, this range is wide [Presentation forms]
|| (0xff01 <= wch && wch <= 0xff5e) // Fullwidth ASCII variants (0xff01 <= wch && wch <= 0xff5e) || // Fullwidth ASCII variants
|| (0xffe0 <= wch && wch <= 0xffe6)) // Fullwidth symbol variants (0xffe0 <= wch && wch <= 0xffe6); // Fullwidth symbol variants
{
return CodepointWidth::Wide;
}
return CodepointWidth::Narrow;
} }
std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::CharToKeyEvents(const wchar_t wch, void Microsoft::Console::Interactivity::CharToKeyEvents(const wchar_t wch, const unsigned int codepage, InputEventQueue& keyEvents)
const unsigned int codepage)
{ {
const short invalidKey = -1; static constexpr short invalidKey = -1;
auto keyState = OneCoreSafeVkKeyScanW(wch); auto keyState = OneCoreSafeVkKeyScanW(wch);
if (keyState == invalidKey) if (keyState == invalidKey)
@ -57,18 +47,19 @@ std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::CharToK
WORD CharType = 0; WORD CharType = 0;
GetStringTypeW(CT_CTYPE3, &wch, 1, &CharType); 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 // It wasn't alphanumeric or determined to be wide by the old algorithm
// if VkKeyScanW fails (char is not in kbd layout), we must // if VkKeyScanW fails (char is not in kbd layout), we must
// emulate the key being input through the numpad // 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 keyState = 0; // SynthesizeKeyboardEvents would rather get 0 than -1
} }
return SynthesizeKeyboardEvents(wch, keyState); SynthesizeKeyboardEvents(wch, keyState, keyEvents);
} }
// Routine Description: // 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 // - deque of KeyEvents that represent the wchar_t being typed
// Note: // Note:
// - will throw exception on error // - 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 vk = LOBYTE(keyState);
const auto virtualScanCode = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC)); const auto sc = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
KeyEvent keyEvent{ true, 1, LOBYTE(keyState), virtualScanCode, wch, 0 }; // 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) if (altGrSet)
{ {
keyEvents.push_back(std::make_unique<KeyEvent>(false, keyEvents.push_back(SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, 0, ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED));
1ui16,
static_cast<WORD>(VK_MENU),
altScanCode,
UNICODE_NULL,
ENHANCED_KEY));
} }
else if (shiftSet) else if (shiftSet)
{ {
keyEvents.push_back(std::make_unique<KeyEvent>(false, keyEvents.push_back(SynthesizeKeyEvent(true, 1, VK_SHIFT, leftShiftScanCode, 0, SHIFT_PRESSED));
1ui16,
static_cast<WORD>(VK_SHIFT),
leftShiftScanCode,
UNICODE_NULL,
0));
} }
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: // Routine Description:
@ -166,64 +122,35 @@ std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::Synthes
// alt + numpad // alt + numpad
// Note: // Note:
// - will throw exception on error // - 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 if (result == 1)
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)
{ {
// 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 // 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. // "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. // 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. // 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 // 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. // 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 for (const auto& ch : charString)
// 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))
{ {
if (ch == 0) const WORD vk = ch - '0' + VK_NUMPAD0;
{ const auto sc = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
break; auto keyEvent = SynthesizeKeyEvent(true, 1, vk, sc, 0, LEFT_ALT_PRESSED);
} keyEvents.push_back(keyEvent);
const WORD virtualKey = ch - '0' + VK_NUMPAD0; keyEvent.Event.KeyEvent.bKeyDown = FALSE;
const auto virtualScanCode = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(virtualKey, MAPVK_VK_TO_VSC)); keyEvents.push_back(keyEvent);
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));
} }
}
// alt keyup // alt keyup
keyEvents.push_back(std::make_unique<KeyEvent>(false, keyEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, wch, 0));
1ui16, }
static_cast<WORD>(VK_MENU),
altScanCode,
wch,
0));
return keyEvents;
} }

View File

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

View File

@ -266,9 +266,7 @@ VOID ConIoSrvComm::ServiceInputPipe()
case CIS_EVENT_TYPE_INPUT: case CIS_EVENT_TYPE_INPUT:
try try
{ {
const auto keyRecord = Event.InputEvent.Record.Event.KeyEvent; HandleGenericKeyEvent(Event.InputEvent.Record, false);
const KeyEvent keyEvent{ keyRecord };
HandleGenericKeyEvent(keyEvent, false);
} }
catch (...) 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 // - deque of KeyEvents that represent the string passed in
// Note: // Note:
// - will throw exception on error // - will throw exception on error
std::deque<std::unique_ptr<IInputEvent>> Clipboard::TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData, InputEventQueue Clipboard::TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
const size_t cchData, const size_t cchData,
const bool bracketedPaste) const bool bracketedPaste)
{ {
THROW_HR_IF_NULL(E_INVALIDARG, pData); THROW_HR_IF_NULL(E_INVALIDARG, pData);
std::deque<std::unique_ptr<IInputEvent>> keyEvents; InputEventQueue keyEvents;
const auto pushControlSequence = [&](const std::wstring_view sequence) { const auto pushControlSequence = [&](const std::wstring_view sequence) {
std::for_each(sequence.begin(), sequence.end(), [&](const auto wch) { 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(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
keyEvents.push_back(std::make_unique<KeyEvent>(false, 1ui16, 0ui16, 0ui16, 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; const auto codepage = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
auto convertedEvents = CharToKeyEvents(currentChar, codepage); CharToKeyEvents(currentChar, codepage, keyEvents);
while (!convertedEvents.empty())
{
keyEvents.push_back(std::move(convertedEvents.front()));
convertedEvents.pop_front();
}
} }
if (bracketedPaste) if (bracketedPaste)
{ {
pushControlSequence(L"\x1b[201~"); pushControlSequence(L"\x1b[201~");
} }
return keyEvents; return keyEvents;
} }

View File

@ -35,9 +35,9 @@ namespace Microsoft::Console::Interactivity::Win32
void Paste(); void Paste();
private: private:
std::deque<std::unique_ptr<IInputEvent>> TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData, InputEventQueue TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
const size_t cchData, const size_t cchData,
const bool bracketedPaste = false); const bool bracketedPaste = false);
void StoreSelectionToClipboard(_In_ const bool fAlsoCopyFormatting); 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 (IsCharacterMessage)
{ {
// If this is a fake character, zero the scancode. // If this is a fake character, zero the scancode.
if (lParam & 0x02000000) 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) if (Message == WM_CHAR || Message == WM_SYSCHAR)
{ {
keyEvent.SetCharData(static_cast<wchar_t>(wParam)); keyEvent.Event.KeyEvent.uChar.UnicodeChar = static_cast<wchar_t>(wParam);
}
else
{
keyEvent.SetCharData(L'\0');
} }
} }
else else
@ -212,8 +208,7 @@ void HandleKeyEvent(const HWND hWnd,
{ {
return; return;
} }
keyEvent.SetActiveModifierKeys(ControlKeyState); keyEvent.Event.KeyEvent.dwControlKeyState = ControlKeyState;
keyEvent.SetCharData(L'\0');
} }
const INPUT_KEY_INFO inputKeyInfo(VirtualKeyCode, ControlKeyState); const INPUT_KEY_INFO inputKeyInfo(VirtualKeyCode, ControlKeyState);
@ -922,26 +917,12 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
break; break;
} }
ULONG EventsWritten = 0; const auto mouseEvent = SynthesizeMouseEvent(
try MousePosition,
{ ConvertMouseButtonState(ButtonFlags, static_cast<UINT>(wParam)),
auto mouseEvent = std::make_unique<MouseEvent>( GetControlKeyState(0),
MousePosition, EventFlags);
ConvertMouseButtonState(ButtonFlags, static_cast<UINT>(wParam)), gci.pInputBuffer->Write(mouseEvent);
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);
}
return FALSE; return FALSE;
} }

View File

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

View File

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

View File

@ -28,9 +28,9 @@ namespace Microsoft::Console::VirtualTerminal
virtual ~IInteractDispatch() = default; virtual ~IInteractDispatch() = default;
#pragma warning(pop) #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; virtual bool WriteString(const std::wstring_view string) = 0;

View File

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

View File

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

View File

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

View File

@ -2,14 +2,10 @@
// Licensed under the MIT license. // Licensed under the MIT license.
#include "precomp.h" #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 "../../../interactivity/inc/VtApiRedirection.hpp"
#include "../../input/terminalInput.hpp"
#include "../types/inc/IInputEvent.hpp"
using namespace WEX::Common; using namespace WEX::Common;
using namespace WEX::Logging; using namespace WEX::Logging;
@ -182,9 +178,8 @@ void InputTest::TerminalInputTests()
break; break;
} }
auto inputEvent = IInputEvent::Create(irTest);
// Send key into object (will trigger callback and verification) // 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."); 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.wVirtualKeyCode = vkey;
irTest.Event.KeyEvent.bKeyDown = FALSE; irTest.Event.KeyEvent.bKeyDown = FALSE;
auto inputEvent = IInputEvent::Create(irTest);
// Send key into object (will trigger callback and verification) // 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."); 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"); Log::Comment(L"Testing MOUSE_EVENT");
irUnhandled.EventType = MOUSE_EVENT; irUnhandled.EventType = MOUSE_EVENT;
auto inputEvent = IInputEvent::Create(irUnhandled); VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irUnhandled), L"Verify MOUSE_EVENT was NOT handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify MOUSE_EVENT was NOT handled.");
Log::Comment(L"Testing WINDOW_BUFFER_SIZE_EVENT"); Log::Comment(L"Testing WINDOW_BUFFER_SIZE_EVENT");
irUnhandled.EventType = WINDOW_BUFFER_SIZE_EVENT; irUnhandled.EventType = WINDOW_BUFFER_SIZE_EVENT;
inputEvent = IInputEvent::Create(irUnhandled); VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irUnhandled), L"Verify WINDOW_BUFFER_SIZE_EVENT was NOT handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify WINDOW_BUFFER_SIZE_EVENT was NOT handled.");
Log::Comment(L"Testing MENU_EVENT"); Log::Comment(L"Testing MENU_EVENT");
irUnhandled.EventType = MENU_EVENT; irUnhandled.EventType = MENU_EVENT;
inputEvent = IInputEvent::Create(irUnhandled); VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irUnhandled), L"Verify MENU_EVENT was NOT handled.");
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify MENU_EVENT was NOT handled.");
// Testing FOCUS_EVENTs is handled by TestFocusEvents // 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 // We're relying on the fact that the INPUT_RECORD version of the ctor is only called by the API
TerminalInput input; TerminalInput input;
INPUT_RECORD irTest = { 0 }; VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(false));
irTest.EventType = FOCUS_EVENT; VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(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::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");
input.SetInputMode(TerminalInput::Mode::FocusEvent, true); input.SetInputMode(TerminalInput::Mode::FocusEvent, true);
{ VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[O"), input.HandleFocus(false));
irTest.Event.FocusEvent.bSetFocus = false; VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[I"), input.HandleFocus(true));
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.");
} }
void InputTest::TerminalInputModifierKeyTests() 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); 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) // 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; irTest.Event.KeyEvent.bKeyDown = TRUE;
// Send key into object (will trigger callback and verification) // Send key into object (will trigger callback and verification)
auto inputEvent = IInputEvent::Create(irTest); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
vkey = VK_SPACE; vkey = VK_SPACE;
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate)); Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
irTest.Event.KeyEvent.wVirtualKeyCode = vkey; irTest.Event.KeyEvent.wVirtualKeyCode = vkey;
irTest.Event.KeyEvent.uChar.UnicodeChar = vkey; irTest.Event.KeyEvent.uChar.UnicodeChar = vkey;
inputEvent = IInputEvent::Create(irTest); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED; uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate)); Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
irTest.Event.KeyEvent.dwControlKeyState = uiKeystate; irTest.Event.KeyEvent.dwControlKeyState = uiKeystate;
inputEvent = IInputEvent::Create(irTest); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
uiKeystate = RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED; uiKeystate = RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED;
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate)); Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
irTest.Event.KeyEvent.dwControlKeyState = uiKeystate; irTest.Event.KeyEvent.dwControlKeyState = uiKeystate;
inputEvent = IInputEvent::Create(irTest); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(inputEvent.get()), 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) 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; irTest.Event.KeyEvent.uChar.UnicodeChar = wch;
// Send key into object (will trigger callback and verification) // Send key into object (will trigger callback and verification)
auto inputEvent = IInputEvent::Create(irTest); VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been.");
VERIFY_ARE_EQUAL(expected, input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
} }
void InputTest::DifferentModifiersTest() void InputTest::DifferentModifiersTest()
@ -706,23 +664,23 @@ void InputTest::BackarrowKeyModeTest()
void InputTest::AutoRepeatModeTest() void InputTest::AutoRepeatModeTest()
{ {
const auto down = std::make_unique<KeyEvent>(true, 1ui16, 'A', 0ui16, 'A', 0ui16); static constexpr auto down = SynthesizeKeyEvent(true, 1, 'A', 0, 'A', 0);
const auto up = std::make_unique<KeyEvent>(false, 1ui16, 'A', 0ui16, 'A', 0ui16); static constexpr auto up = SynthesizeKeyEvent(false, 1, 'A', 0, 'A', 0);
TerminalInput input; TerminalInput input;
Log::Comment(L"Sending repeating keypresses with DECARM disabled."); Log::Comment(L"Sending repeating keypresses with DECARM disabled.");
input.SetInputMode(TerminalInput::Mode::AutoRepeat, false); input.SetInputMode(TerminalInput::Mode::AutoRepeat, false);
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get())); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down.get())); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down.get())); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up.get())); VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up));
Log::Comment(L"Sending repeating keypresses with DECARM enabled."); Log::Comment(L"Sending repeating keypresses with DECARM enabled.");
input.SetInputMode(TerminalInput::Mode::AutoRepeat, true); 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));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get())); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get())); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up.get())); VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up));
} }

View File

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

View File

@ -6,8 +6,9 @@
#include <til/unicode.h> #include <til/unicode.h>
#include "../../interactivity/inc/VtApiRedirection.hpp"
#include "../../inc/unicode.hpp" #include "../../inc/unicode.hpp"
#include "../../interactivity/inc/VtApiRedirection.hpp"
#include "../types/inc/IInputEvent.hpp"
using namespace Microsoft::Console::VirtualTerminal; using namespace Microsoft::Console::VirtualTerminal;
@ -268,45 +269,45 @@ void TerminalInput::ForceDisableWin32InputMode(const bool win32InputMode) noexce
_forceDisableWin32InputMode = win32InputMode; _forceDisableWin32InputMode = win32InputMode;
} }
static const std::span<const TermKeyMap> _getKeyMapping(const KeyEvent& keyEvent, static std::span<const TermKeyMap> _getKeyMapping(const KEY_EVENT_RECORD& keyEvent, const bool ansiMode, const bool cursorApplicationMode, const bool keypadApplicationMode) noexcept
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 (ansiMode)
{ {
if (keyEvent.IsCursorKey()) if (isCursorKey)
{ {
if (cursorApplicationMode) if (cursorApplicationMode)
{ {
return { s_cursorKeysApplicationMapping.data(), s_cursorKeysApplicationMapping.size() }; return s_cursorKeysApplicationMapping;
} }
else else
{ {
return { s_cursorKeysNormalMapping.data(), s_cursorKeysNormalMapping.size() }; return s_cursorKeysNormalMapping;
} }
} }
else else
{ {
if (keypadApplicationMode) if (keypadApplicationMode)
{ {
return { s_keypadApplicationMapping.data(), s_keypadApplicationMapping.size() }; return s_keypadApplicationMapping;
} }
else else
{ {
return { s_keypadNumericMapping.data(), s_keypadNumericMapping.size() }; return s_keypadNumericMapping;
} }
} }
} }
else else
{ {
if (keyEvent.IsCursorKey()) if (isCursorKey)
{ {
return { s_cursorKeysVt52Mapping.data(), s_cursorKeysVt52Mapping.size() }; return s_cursorKeysVt52Mapping;
} }
else 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 // - keyMapping - Array of key mappings to search
// Return Value: // Return Value:
// - Has value if there was a match to a key translation. // - 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 std::span<const TermKeyMap> keyMapping) noexcept
{ {
for (auto& map : keyMapping) 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 // 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 // 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 // The modifier mapping expects certain modifier keys to be
// pressed. Check those as well. // pressed. Check those as well.
modifiersMatch = modifiersMatch =
(WI_IsFlagSet(map.modifiers, SHIFT_PRESSED) == keyEvent.IsShiftPressed()) && WI_IsAnyFlagSet(map.modifiers, SHIFT_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED) &&
(WI_IsAnyFlagSet(map.modifiers, ALT_PRESSED) == keyEvent.IsAltPressed()) && WI_IsAnyFlagSet(map.modifiers, ALT_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) &&
(WI_IsAnyFlagSet(map.modifiers, CTRL_PRESSED) == keyEvent.IsCtrlPressed()); WI_IsAnyFlagSet(map.modifiers, CTRL_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
} }
if (modifiersMatch) 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. // 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. // 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)) if (const auto match = _searchKeyMapping(keyEvent, s_modifierKeyMapping))
{ {
const auto& v = match.value(); const auto& v = match.value();
if (!v.sequence.empty()) if (!v.sequence.empty())
{ {
const auto shift = keyEvent.IsShiftPressed(); const auto shift = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED);
const auto alt = keyEvent.IsAltPressed(); const auto alt = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED);
const auto ctrl = keyEvent.IsCtrlPressed(); const auto ctrl = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
StringType str{ v.sequence }; StringType str{ v.sequence };
str.at(str.size() - 2) = L'1' + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0); str.at(str.size() - 2) = L'1' + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0);
return str; return str;
@ -407,12 +408,12 @@ TerminalInput::OutputType TerminalInput::_searchWithModifier(const KeyEvent& key
const auto slashVkey = LOBYTE(slashKeyScan); const auto slashVkey = LOBYTE(slashKeyScan);
const auto questionMarkVkey = LOBYTE(questionMarkKeyScan); const auto questionMarkVkey = LOBYTE(questionMarkKeyScan);
const auto ctrl = keyEvent.IsCtrlPressed(); const auto ctrl = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
const auto alt = keyEvent.IsAltPressed(); const auto alt = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED);
const auto shift = keyEvent.IsShiftPressed(); const auto shift = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED);
// From the KeyEvent we're translating, synthesize the equivalent VkKeyScan result // 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 | const short keyScanFromEvent = vkey |
(shift ? 0x100 : 0) | (shift ? 0x100 : 0) |
(ctrl ? 0x200 : 0) | (ctrl ? 0x200 : 0) |
@ -474,20 +475,15 @@ TerminalInput::OutputType TerminalInput::MakeOutput(const std::wstring_view& str
// Return Value: // 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 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. // - 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 // On key presses, prepare to translate to VT compatible sequences
if (pInEvent->EventType() != InputEventType::KeyEvent) if (event.EventType != KEY_EVENT)
{ {
return MakeUnhandled(); 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. // 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. // 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. // 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) // 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 this is a release of the last recorded key press, we can reset that.
if (matchingLastKeyPress) 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. // the event, otherwise the key press can still end up being submitted.
return MakeOutput({}); return MakeOutput({});
} }
_lastVirtualKeyCode = keyEvent.GetVirtualKeyCode(); _lastVirtualKeyCode = keyEvent.wVirtualKeyCode;
// The VK_BACK key depends on the state of Backarrow Key mode (DECBKM). // 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 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. // 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'; const auto seq = backarrowMode ? L'\x08' : L'\x7f';
// The Alt modifier adds an escape prefix. // The Alt modifier adds an escape prefix.
if (keyEvent.IsAltPressed()) if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED))
{ {
return _makeEscapedOutput(seq); 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 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 // When reset, we fall through to the default behavior, which is to send just
// CR, or when the Ctrl modifier is pressed, just LF. // 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"); 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. // 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. // 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. // 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. // --> 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); WI_ClearAllFlags(keyEvent.dwControlKeyState, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
keyEvent.DeactivateModifierKey(ModifierKeyState::RightAlt);
} }
// The Alt modifier initiates a so called "escape sequence". // 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. // This section in particular handles Alt+Ctrl combinations though.
// The Ctrl modifier causes all of the char code's bits except // The Ctrl modifier causes all of the char code's bits except
// for the 5 least significant ones to be zeroed out. // 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 ch = keyEvent.uChar.UnicodeChar;
const auto vkey = keyEvent.GetVirtualKeyCode(); 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 // Luckily the numerical values of the ASCII characters and virtual key codes
// of <Space> and A-Z, as used below, are numerically identical. // of <Space> and A-Z, as used below, are numerically identical.
// -> Get the char from the virtual key if it's 0. // -> 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. // 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. // 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 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)) 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, // This section is similar to the Alt modifier section above,
// but handles cases without Ctrl modifiers. // 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 // 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. // -> Send a "null input sequence" in that case.
// We don't need to handle other kinds of Ctrl combinations, // We don't need to handle other kinds of Ctrl combinations,
// as we rely on the caller to pretranslate those to characters for us. // 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 ch = keyEvent.uChar.UnicodeChar;
const auto vkey = keyEvent.GetVirtualKeyCode(); const auto vkey = keyEvent.wVirtualKeyCode;
// Currently, when we're called with Ctrl+@, ch will be 0, since Ctrl+@ equals a null byte. // 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 @). // 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) if (ch == UNICODE_NULL)
{ {
// -> Try to infer the character from the vkey. // -> 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) if (mappedChar)
{ {
// Pressing the control key causes all bits but the 5 least // 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 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(); 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. // Turns an KEY_EVENT_RECORD into a win32-input-mode VT sequence.
// It allows us to send KEY_EVENT_RECORD data losslessly to conhost. // 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. // .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. // 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 kd = gsl::narrow_cast<uint16_t>(key.bKeyDown ? 1 : 0);
const auto rc = gsl::narrow_cast<uint16_t>(key.GetRepeatCount()); const auto rc = gsl::narrow_cast<uint16_t>(key.wRepeatCount);
const auto vk = gsl::narrow_cast<uint16_t>(key.GetVirtualKeyCode()); const auto vk = gsl::narrow_cast<uint16_t>(key.wVirtualKeyCode);
const auto sc = gsl::narrow_cast<uint16_t>(key.GetVirtualScanCode()); const auto sc = gsl::narrow_cast<uint16_t>(key.wVirtualScanCode);
const auto uc = gsl::narrow_cast<uint16_t>(key.GetCharData()); const auto uc = gsl::narrow_cast<uint16_t>(key.uChar.UnicodeChar);
const auto cs = gsl::narrow_cast<uint16_t>(key.GetActiveModifierKeys()); const auto cs = gsl::narrow_cast<uint16_t>(key.dwControlKeyState);
// Sequences are formatted as follows: // Sequences are formatted as follows:
// //

View File

@ -1,22 +1,8 @@
/*+ // Copyright (c) Microsoft Corporation.
Copyright (c) Microsoft Corporation // Licensed under the MIT license.
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
--*/
#pragma once #pragma once
#include "../../types/inc/IInputEvent.hpp"
namespace Microsoft::Console::VirtualTerminal namespace Microsoft::Console::VirtualTerminal
{ {
class TerminalInput final class TerminalInput final
@ -34,7 +20,7 @@ namespace Microsoft::Console::VirtualTerminal
static [[nodiscard]] OutputType MakeUnhandled() noexcept; static [[nodiscard]] OutputType MakeUnhandled() noexcept;
static [[nodiscard]] OutputType MakeOutput(const std::wstring_view& str); 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 HandleFocus(bool focused) const;
[[nodiscard]] OutputType HandleMouse(til::point position, unsigned int button, short modifierKeyState, short delta, MouseButtonState state); [[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); [[nodiscard]] OutputType _makeCharOutput(wchar_t ch);
static [[nodiscard]] OutputType _makeEscapedOutput(wchar_t wch); static [[nodiscard]] OutputType _makeEscapedOutput(wchar_t wch);
static [[nodiscard]] OutputType _makeWin32Output(const KeyEvent& key); static [[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key);
static [[nodiscard]] OutputType _searchWithModifier(const KeyEvent& keyEvent); static [[nodiscard]] OutputType _searchWithModifier(const KEY_EVENT_RECORD& keyEvent);
#pragma region MouseInputState Management #pragma region MouseInputState Management
// These methods are defined in mouseInputState.cpp // 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) if (wch == UNICODE_ETX && !writeAlt)
{ {
// This is Ctrl+C, which is handled specially by the host. // 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); static constexpr auto keyDown = SynthesizeKeyEvent(true, 1, L'C', 0, UNICODE_ETX, LEFT_CTRL_PRESSED);
success = _pDispatch->WriteCtrlKey(keyDown) && _pDispatch->WriteCtrlKey(keyUp); 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') else if (wch >= '\x0' && wch < '\x20')
{ {
@ -278,10 +281,10 @@ bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view st
// similar to TerminalInput::_SendInputSequence // similar to TerminalInput::_SendInputSequence
if (!string.empty()) if (!string.empty())
{ {
std::deque<std::unique_ptr<IInputEvent>> inputEvents; InputEventQueue inputEvents;
for (const auto& wch : string) 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); return _pDispatch->WriteInput(inputEvents);
} }
@ -558,7 +561,7 @@ bool InputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch, void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
const short vkey, const short vkey,
const DWORD modifierState, const DWORD modifierState,
std::vector<INPUT_RECORD>& input) InputEventQueue& input)
{ {
input.reserve(input.size() + 8); 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 ctrl = WI_IsFlagSet(modifierState, LEFT_CTRL_PRESSED);
const auto alt = WI_IsFlagSet(modifierState, LEFT_ALT_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; DWORD currentModifiers = 0;
if (shift) if (shift)
{ {
WI_SetFlag(currentModifiers, SHIFT_PRESSED); WI_SetFlag(currentModifiers, SHIFT_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = TRUE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers; next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT; next.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_SHIFT, MAPVK_VK_TO_VSC)); 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); input.push_back(next);
} }
if (alt) if (alt)
{ {
WI_SetFlag(currentModifiers, LEFT_ALT_PRESSED); WI_SetFlag(currentModifiers, LEFT_ALT_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = TRUE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers; next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_MENU; next.Event.KeyEvent.wVirtualKeyCode = VK_MENU;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_MENU, MAPVK_VK_TO_VSC)); 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); input.push_back(next);
} }
if (ctrl) if (ctrl)
{ {
WI_SetFlag(currentModifiers, LEFT_CTRL_PRESSED); WI_SetFlag(currentModifiers, LEFT_CTRL_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = TRUE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers; next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL; next.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_CONTROL, MAPVK_VK_TO_VSC)); 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); input.push_back(next);
} }
@ -616,40 +606,30 @@ void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
// through on the KeyPress. // through on the KeyPress.
_GetSingleKeypress(wch, vkey, modifierState, input); _GetSingleKeypress(wch, vkey, modifierState, input);
next.Event.KeyEvent.bKeyDown = FALSE;
if (ctrl) if (ctrl)
{ {
WI_ClearFlag(currentModifiers, LEFT_CTRL_PRESSED); WI_ClearFlag(currentModifiers, LEFT_CTRL_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = FALSE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers; next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL; next.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_CONTROL, MAPVK_VK_TO_VSC)); 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); input.push_back(next);
} }
if (alt) if (alt)
{ {
WI_ClearFlag(currentModifiers, LEFT_ALT_PRESSED); WI_ClearFlag(currentModifiers, LEFT_ALT_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = FALSE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers; next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_MENU; next.Event.KeyEvent.wVirtualKeyCode = VK_MENU;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_MENU, MAPVK_VK_TO_VSC)); 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); input.push_back(next);
} }
if (shift) if (shift)
{ {
WI_ClearFlag(currentModifiers, SHIFT_PRESSED); WI_ClearFlag(currentModifiers, SHIFT_PRESSED);
next.EventType = KEY_EVENT;
next.Event.KeyEvent.bKeyDown = FALSE;
next.Event.KeyEvent.dwControlKeyState = currentModifiers; next.Event.KeyEvent.dwControlKeyState = currentModifiers;
next.Event.KeyEvent.wRepeatCount = 1;
next.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT; next.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT;
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_SHIFT, MAPVK_VK_TO_VSC)); 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); input.push_back(next);
} }
} }
@ -668,21 +648,14 @@ void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
void InputStateMachineEngine::_GetSingleKeypress(const wchar_t wch, void InputStateMachineEngine::_GetSingleKeypress(const wchar_t wch,
const short vkey, const short vkey,
const DWORD modifierState, const DWORD modifierState,
std::vector<INPUT_RECORD>& input) InputEventQueue& input)
{ {
input.reserve(input.size() + 2); 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); input.push_back(rec);
rec.Event.KeyEvent.bKeyDown = FALSE; rec.Event.KeyEvent.bKeyDown = FALSE;
input.push_back(rec); 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) 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. // 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; InputEventQueue inputEvents;
_GenerateWrappedSequence(wch, vkey, modifierState, input); _GenerateWrappedSequence(wch, vkey, modifierState, inputEvents);
auto inputEvents = IInputEvent::Create(std::span{ input });
return _pDispatch->WriteInput(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. // - 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) bool InputStateMachineEngine::_WriteMouseEvent(const til::point uiPos, const DWORD buttonState, const DWORD controlKeyState, const DWORD eventFlags)
{ {
INPUT_RECORD rgInput; const auto rgInput = SynthesizeMouseEvent(uiPos, buttonState, controlKeyState, eventFlags);
rgInput.EventType = MOUSE_EVENT; return _pDispatch->WriteInput({ &rgInput, 1 });
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);
} }
// Method Description: // 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. // - parameters: the list of numbers to parse into values for the KeyEvent.
// Return Value: // Return Value:
// - The deserialized KeyEvent. // - The deserialized KeyEvent.
KeyEvent InputStateMachineEngine::_GenerateWin32Key(const VTParameters parameters) INPUT_RECORD InputStateMachineEngine::_GenerateWin32Key(const VTParameters& parameters)
{ {
// Sequences are formatted as follows: // 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'. // 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'. // Cs: the value of dwControlKeyState - any number. If omitted, defaults to '0'.
// Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'. // Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'.
return SynthesizeKeyEvent(
auto key = KeyEvent(); parameters.at(3).value_or(0),
key.SetVirtualKeyCode(::base::saturated_cast<WORD>(parameters.at(0).value_or(0))); ::base::saturated_cast<uint16_t>(parameters.at(5).value_or(1)),
key.SetVirtualScanCode(::base::saturated_cast<WORD>(parameters.at(1).value_or(0))); ::base::saturated_cast<uint16_t>(parameters.at(0).value_or(0)),
key.SetCharData(::base::saturated_cast<wchar_t>(parameters.at(2).value_or(0))); ::base::saturated_cast<uint16_t>(parameters.at(1).value_or(0)),
key.SetKeyDown(parameters.at(3).value_or(0)); ::base::saturated_cast<wchar_t>(parameters.at(2).value_or(0)),
key.SetActiveModifierKeys(::base::saturated_cast<DWORD>(parameters.at(4).value_or(0))); ::base::saturated_cast<uint32_t>(parameters.at(4).value_or(0)));
key.SetRepeatCount(::base::saturated_cast<WORD>(parameters.at(5).value_or(1)));
return key;
} }

View File

@ -197,17 +197,17 @@ namespace Microsoft::Console::VirtualTerminal
void _GenerateWrappedSequence(const wchar_t wch, void _GenerateWrappedSequence(const wchar_t wch,
const short vkey, const short vkey,
const DWORD modifierState, const DWORD modifierState,
std::vector<INPUT_RECORD>& input); InputEventQueue& input);
void _GetSingleKeypress(const wchar_t wch, void _GetSingleKeypress(const wchar_t wch,
const short vkey, const short vkey,
const DWORD modifierState, const DWORD modifierState,
std::vector<INPUT_RECORD>& input); InputEventQueue& input);
bool _GetWindowManipulationType(const std::span<const size_t> parameters, bool _GetWindowManipulationType(const std::span<const size_t> parameters,
unsigned int& function) const noexcept; 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); 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 // Take all the characters out of the input records here, and put them into
// the input state machine. // the input state machine.
auto inputRecords = IInputEvent::ToInputRecords(inEvents);
std::wstring vtseq = L""; std::wstring vtseq = L"";
for (auto& inRec : inputRecords) for (auto& inRec : inputRecords)
{ {
@ -96,10 +95,8 @@ public:
Log::Comment(L"String processed"); 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 // 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, // C0Test. For ^C, we always send a keydown and a key up event, however,
// both calls to WriteCtrlKey happen in one single call to // both calls to WriteCtrlKey happen in one single call to
@ -146,10 +143,8 @@ public:
vExpectedInput.clear(); 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) for (auto expected : vExpectedInput)
{ {
Log::Comment( Log::Comment(
@ -211,9 +206,9 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest
TestState testState; TestState testState;
void RoundtripTerminalInputCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents); void RoundtripTerminalInputCallback(const std::span<const INPUT_RECORD>& inEvents);
void TestInputCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents); void TestInputCallback(const std::span<const INPUT_RECORD>& inEvents);
void TestInputStringCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents); void TestInputStringCallback(const std::span<const INPUT_RECORD>& inEvents);
std::wstring GenerateSgrMouseSequence(const CsiMouseButtonCodes button, std::wstring GenerateSgrMouseSequence(const CsiMouseButtonCodes button,
const unsigned short modifiers, const unsigned short modifiers,
const til::point position, const til::point position,
@ -318,11 +313,11 @@ void InputEngineTest::VerifyExpectedInputDrained()
class Microsoft::Console::VirtualTerminal::TestInteractDispatch final : public IInteractDispatch class Microsoft::Console::VirtualTerminal::TestInteractDispatch final : public IInteractDispatch
{ {
public: 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); _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, virtual bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const VTParameter parameter1, const VTParameter parameter1,
const VTParameter parameter2) override; // DTTERM_WindowManipulation const VTParameter parameter2) override; // DTTERM_WindowManipulation
@ -336,29 +331,27 @@ public:
virtual bool FocusChanged(const bool focused) const override; virtual bool FocusChanged(const bool focused) const override;
private: 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 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) : _In_ TestState* testState) :
_pfnWriteInputCallback(pfn), _pfnWriteInputCallback(pfn),
_testState(testState) _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); _pfnWriteInputCallback(inputEvents);
return true; return true;
} }
bool TestInteractDispatch::WriteCtrlKey(const KeyEvent& event) bool TestInteractDispatch::WriteCtrlKey(const INPUT_RECORD& event)
{ {
VERIFY_IS_TRUE(_testState->_expectSendCtrlC); VERIFY_IS_TRUE(_testState->_expectSendCtrlC);
std::deque<std::unique_ptr<IInputEvent>> inputEvents; return WriteInput({ &event, 1 });
inputEvents.push_back(std::make_unique<KeyEvent>(event));
return WriteInput(inputEvents);
} }
bool TestInteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulationType function, 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) bool TestInteractDispatch::WriteString(const std::wstring_view string)
{ {
std::deque<std::unique_ptr<IInputEvent>> keyEvents; InputEventQueue keyEvents;
for (const auto& wch : string) for (const auto& wch : string)
{ {
// We're forcing the translation to CP_USA, so that it'll be constant // We're forcing the translation to CP_USA, so that it'll be constant
// regardless of the CP the test is running in // regardless of the CP the test is running in
auto convertedEvents = Microsoft::Console::Interactivity::CharToKeyEvents(wch, CP_USA); Microsoft::Console::Interactivity::CharToKeyEvents(wch, CP_USA, keyEvents);
std::move(convertedEvents.begin(),
convertedEvents.end(),
std::back_inserter(keyEvents));
} }
return WriteInput(keyEvents); return WriteInput(keyEvents);
@ -966,12 +956,12 @@ void InputEngineTest::AltIntermediateTest()
// Create the callback that's fired when the state machine wants to write // 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 // input. We'll take the events and put them straight into the
// TerminalInput. // TerminalInput.
auto pfnInputStateMachineCallback = [&](std::deque<std::unique_ptr<IInputEvent>>& inEvents) { auto pfnInputStateMachineCallback = [&](const std::span<const INPUT_RECORD>& inEvents) {
for (auto& ev : 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 }; std::vector<VTParameter> params{ 1 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }); auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode()); VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(0, key.GetVirtualScanCode()); VERIFY_ARE_EQUAL(0, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\0', key.GetCharData()); VERIFY_ARE_EQUAL(L'\0', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(false, key.IsKeyDown()); VERIFY_ARE_EQUAL(FALSE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys()); VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.GetRepeatCount()); VERIFY_ARE_EQUAL(1, key.wRepeatCount);
} }
{ {
std::vector<VTParameter> params{ 1, 2 }; std::vector<VTParameter> params{ 1, 2 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }); auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode()); VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode()); VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\0', key.GetCharData()); VERIFY_ARE_EQUAL(L'\0', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(false, key.IsKeyDown()); VERIFY_ARE_EQUAL(FALSE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys()); VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.GetRepeatCount()); VERIFY_ARE_EQUAL(1, key.wRepeatCount);
} }
{ {
std::vector<VTParameter> params{ 1, 2, 3 }; std::vector<VTParameter> params{ 1, 2, 3 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }); auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode()); VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode()); VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData()); VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(false, key.IsKeyDown()); VERIFY_ARE_EQUAL(FALSE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys()); VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.GetRepeatCount()); VERIFY_ARE_EQUAL(1, key.wRepeatCount);
} }
{ {
std::vector<VTParameter> params{ 1, 2, 3, 4 }; std::vector<VTParameter> params{ 1, 2, 3, 4 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }); auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode()); VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode()); VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData()); VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(true, key.IsKeyDown()); VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys()); VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.GetRepeatCount()); VERIFY_ARE_EQUAL(1, key.wRepeatCount);
} }
{ {
std::vector<VTParameter> params{ 1, 2, 3, 1 }; std::vector<VTParameter> params{ 1, 2, 3, 1 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }); auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode()); VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode()); VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData()); VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(true, key.IsKeyDown()); VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys()); VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.GetRepeatCount()); VERIFY_ARE_EQUAL(1, key.wRepeatCount);
} }
{ {
std::vector<VTParameter> params{ 1, 2, 3, 4, 5 }; std::vector<VTParameter> params{ 1, 2, 3, 4, 5 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }); auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode()); VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode()); VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData()); VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(true, key.IsKeyDown()); VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
VERIFY_ARE_EQUAL(0x5u, key.GetActiveModifierKeys()); VERIFY_ARE_EQUAL(0x5u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(1, key.GetRepeatCount()); VERIFY_ARE_EQUAL(1, key.wRepeatCount);
} }
{ {
std::vector<VTParameter> params{ 1, 2, 3, 4, 5, 6 }; std::vector<VTParameter> params{ 1, 2, 3, 4, 5, 6 };
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }); auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode()); VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode()); VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData()); VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL(true, key.IsKeyDown()); VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
VERIFY_ARE_EQUAL(0x5u, key.GetActiveModifierKeys()); VERIFY_ARE_EQUAL(0x5u, key.dwControlKeyState);
VERIFY_ARE_EQUAL(6, key.GetRepeatCount()); VERIFY_ARE_EQUAL(6, key.wRepeatCount);
} }
} }
@ -1580,26 +1570,26 @@ void InputEngineTest::TestWin32InputOptionals()
provideRepeatCount ? 6 : 0, 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, VERIFY_ARE_EQUAL((provideVirtualKeyCode && numParams > 0) ? 1 : 0,
key.GetVirtualKeyCode()); key.wVirtualKeyCode);
VERIFY_ARE_EQUAL((provideVirtualScanCode && numParams > 1) ? 2 : 0, VERIFY_ARE_EQUAL((provideVirtualScanCode && numParams > 1) ? 2 : 0,
key.GetVirtualScanCode()); key.wVirtualScanCode);
VERIFY_ARE_EQUAL((provideCharData && numParams > 2) ? L'\x03' : L'\0', VERIFY_ARE_EQUAL((provideCharData && numParams > 2) ? L'\x03' : L'\0',
key.GetCharData()); key.uChar.UnicodeChar);
VERIFY_ARE_EQUAL((provideKeyDown && numParams > 3) ? true : false, VERIFY_ARE_EQUAL((provideKeyDown && numParams > 3) ? TRUE : FALSE,
key.IsKeyDown()); key.bKeyDown);
VERIFY_ARE_EQUAL((provideActiveModifierKeys && numParams > 4) ? 5u : 0u, VERIFY_ARE_EQUAL((provideActiveModifierKeys && numParams > 4) ? 5u : 0u,
key.GetActiveModifierKeys()); key.dwControlKeyState);
if (numParams == 6) if (numParams == 6)
{ {
VERIFY_ARE_EQUAL((provideRepeatCount) ? 6 : 0, VERIFY_ARE_EQUAL((provideRepeatCount) ? 6 : 0,
key.GetRepeatCount()); key.wRepeatCount);
} }
else else
{ {
VERIFY_ARE_EQUAL((provideRepeatCount && numParams > 5) ? 6 : 1, 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.
Copyright (c) Microsoft Corporation // Licensed under the MIT license.
Module Name:
- IInputEvent.hpp
Abstract:
- Internal representation of public INPUT_RECORD struct.
Author:
- Austin Diviness (AustDi) 18-Aug-2017
--*/
#pragma once #pragma once
#include <wil/common.h> #include <til/small_vector.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
#define ALT_PRESSED (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED) #define ALT_PRESSED (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)
#define CTRL_PRESSED (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED) #define CTRL_PRESSED (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)
#define MOD_PRESSED (SHIFT_PRESSED | ALT_PRESSED | 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): using InputEventQueue = til::small_vector<INPUT_RECORD, 16>;
// 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;
};
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, return INPUT_RECORD{
LeftAlt, .EventType = KEY_EVENT,
RightCtrl, .Event = {
LeftCtrl, .KeyEvent = {
Shift, .bKeyDown = bKeyDown,
NumLock, .wRepeatCount = wRepeatCount,
ScrollLock, .wVirtualKeyCode = wVirtualKeyCode,
CapsLock, .wVirtualScanCode = wVirtualScanCode,
EnhancedKey, .uChar = { .UnicodeChar = UnicodeChar },
NlsDbcsChar, .dwControlKeyState = dwControlKeyState,
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
}; };
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 constexpr INPUT_RECORD SynthesizeMouseEvent(til::point dwMousePosition, uint32_t dwButtonState, uint32_t dwControlKeyState, uint32_t dwEventFlags)
std::wostream& operator<<(std::wostream& stream, const KeyEvent* const pKeyEvent);
#endif
class MouseEvent : public IInputEvent
{ {
public: return INPUT_RECORD{
constexpr MouseEvent(const MOUSE_EVENT_RECORD& record) : .EventType = MOUSE_EVENT,
_position{ til::wrap_coord(record.dwMousePosition) }, .Event = {
_buttonState{ record.dwButtonState }, .MouseEvent = {
_activeModifierKeys{ record.dwControlKeyState }, .dwMousePosition = {
_eventFlags{ record.dwEventFlags } ::base::saturated_cast<SHORT>(dwMousePosition.x),
{ ::base::saturated_cast<SHORT>(dwMousePosition.y),
} },
.dwButtonState = dwButtonState,
.dwControlKeyState = dwControlKeyState,
.dwEventFlags = dwEventFlags,
},
},
};
}
constexpr MouseEvent(const til::point position, constexpr INPUT_RECORD SynthesizeWindowBufferSizeEvent(til::size dwSize)
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
{ {
public: return INPUT_RECORD{
constexpr WindowBufferSizeEvent(const WINDOW_BUFFER_SIZE_RECORD& record) : .EventType = WINDOW_BUFFER_SIZE_EVENT,
_size{ til::wrap_coord_size(record.dwSize) } .Event = {
{ .WindowBufferSizeEvent = {
} .dwSize = {
::base::saturated_cast<SHORT>(dwSize.width),
::base::saturated_cast<SHORT>(dwSize.height),
},
},
},
};
}
constexpr WindowBufferSizeEvent(const til::size size) : constexpr INPUT_RECORD SynthesizeMenuEvent(uint32_t dwCommandId)
_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
{ {
public: return INPUT_RECORD{
constexpr MenuEvent(const MENU_EVENT_RECORD& record) : .EventType = MENU_EVENT,
_commandId{ record.dwCommandId } .Event = {
{ .MenuEvent = {
} .dwCommandId = dwCommandId,
},
},
};
}
constexpr MenuEvent(const UINT commandId) : constexpr INPUT_RECORD SynthesizeFocusEvent(bool bSetFocus)
_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
{ {
public: return INPUT_RECORD{
constexpr FocusEvent(const FOCUS_EVENT_RECORD& record) : .EventType = FOCUS_EVENT,
_focus{ !!record.bSetFocus } .Event = {
{ .FocusEvent = {
} .bSetFocus = 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

View File

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

View File

@ -24,30 +24,9 @@
<ClCompile Include="..\colorTable.cpp"> <ClCompile Include="..\colorTable.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </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"> <ClCompile Include="..\Viewport.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\WindowBufferSizeEvent.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\precomp.cpp"> <ClCompile Include="..\precomp.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
@ -69,9 +48,6 @@
<ClCompile Include="..\ThemeUtils.cpp"> <ClCompile Include="..\ThemeUtils.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\Environment.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\sgrStack.cpp"> <ClCompile Include="..\sgrStack.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
@ -116,24 +92,6 @@
<ClInclude Include="..\UiaTextRangeBase.hpp"> <ClInclude Include="..\UiaTextRangeBase.hpp">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </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"> <ClInclude Include="..\ScreenInfoUiaProviderBase.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
@ -152,9 +110,6 @@
<ClInclude Include="..\inc\ThemeUtils.h"> <ClInclude Include="..\inc\ThemeUtils.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\inc\Environment.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\sgrStack.hpp"> <ClInclude Include="..\inc\sgrStack.hpp">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>

View File

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

View File

@ -45,9 +45,28 @@
</Expand> </Expand>
</Type> </Type>
<Type Name="KeyEvent"> <Type Name="_KEY_EVENT_RECORD">
<DisplayString Condition="_keyDown">{{↓ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}}</DisplayString> <DisplayString Condition="bKeyDown != 0">↓ rep:{wRepeatCount} vk:{wVirtualKeyCode} sc:{wVirtualScanCode} ch:{uChar.UnicodeChar} ctrl:{(unsigned short)dwControlKeyState,x}</DisplayString>
<DisplayString Condition="!_keyDown">{{↑ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}}</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>
<Type Name="til::size"> <Type Name="til::size">