mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
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:
parent
e9c8391fd5
commit
5b44476048
@ -511,7 +511,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// modifier key. We'll wait for a real keystroke to dismiss the
|
||||
// GH #7395 - don't update selection when taking PrintScreen
|
||||
// selection.
|
||||
return HasSelection() && !KeyEvent::IsModifierKey(vkey) && vkey != VK_SNAPSHOT;
|
||||
return HasSelection() && ::Microsoft::Terminal::Core::Terminal::IsInputKey(vkey);
|
||||
}
|
||||
|
||||
bool ControlCore::TryMarkModeKeybinding(const WORD vkey,
|
||||
|
||||
@ -661,7 +661,7 @@ bool Terminal::SendKeyEvent(const WORD vkey,
|
||||
// modifier key. We'll wait for a real keystroke to snap to the bottom.
|
||||
// GH#6481 - Additionally, make sure the key was actually pressed. This
|
||||
// check will make sure we behave the same as before GH#6309
|
||||
if (!KeyEvent::IsModifierKey(vkey) && keyDown)
|
||||
if (IsInputKey(vkey) && keyDown)
|
||||
{
|
||||
TrySnapOnInput();
|
||||
}
|
||||
@ -714,8 +714,8 @@ bool Terminal::SendKeyEvent(const WORD vkey,
|
||||
return false;
|
||||
}
|
||||
|
||||
const KeyEvent keyEv{ keyDown, 1, vkey, sc, ch, states.Value() };
|
||||
return _handleTerminalInputResult(_terminalInput.HandleKey(&keyEv));
|
||||
const auto keyEv = SynthesizeKeyEvent(keyDown, 1, vkey, sc, ch, states.Value());
|
||||
return _handleTerminalInputResult(_terminalInput.HandleKey(keyEv));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@ -791,8 +791,8 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro
|
||||
MarkOutputStart();
|
||||
}
|
||||
|
||||
const KeyEvent keyDown{ true, 1, vkey, scanCode, ch, states.Value() };
|
||||
return _handleTerminalInputResult(_terminalInput.HandleKey(&keyDown));
|
||||
const auto keyDown = SynthesizeKeyEvent(true, 1, vkey, scanCode, ch, states.Value());
|
||||
return _handleTerminalInputResult(_terminalInput.HandleKey(keyDown));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@ -60,6 +60,22 @@ class Microsoft::Terminal::Core::Terminal final :
|
||||
using RenderSettings = Microsoft::Console::Render::RenderSettings;
|
||||
|
||||
public:
|
||||
static constexpr bool IsInputKey(WORD vkey)
|
||||
{
|
||||
return vkey != VK_CONTROL &&
|
||||
vkey != VK_LCONTROL &&
|
||||
vkey != VK_RCONTROL &&
|
||||
vkey != VK_MENU &&
|
||||
vkey != VK_LMENU &&
|
||||
vkey != VK_RMENU &&
|
||||
vkey != VK_SHIFT &&
|
||||
vkey != VK_LSHIFT &&
|
||||
vkey != VK_RSHIFT &&
|
||||
vkey != VK_LWIN &&
|
||||
vkey != VK_RWIN &&
|
||||
vkey != VK_SNAPSHOT;
|
||||
}
|
||||
|
||||
Terminal();
|
||||
|
||||
void Create(til::size viewportSize,
|
||||
|
||||
@ -87,4 +87,6 @@ void RedrawCommandLine(COOKED_READ_DATA& cookedReadData);
|
||||
bool IsWordDelim(const wchar_t wch);
|
||||
bool IsWordDelim(const std::wstring_view charData);
|
||||
|
||||
bool IsValidStringBuffer(_In_ bool Unicode, _In_reads_bytes_(Size) PVOID Buffer, _In_ ULONG Size, _In_ ULONG Count, ...);
|
||||
|
||||
void SetCurrentCommandLine(COOKED_READ_DATA& cookedReadData, _In_ CommandHistory::Index Index);
|
||||
|
||||
@ -462,18 +462,13 @@ void ConsoleImeInfo::_InsertConvertedString(const std::wstring_view text)
|
||||
}
|
||||
|
||||
const auto dwControlKeyState = GetControlKeyState(0);
|
||||
std::deque<std::unique_ptr<IInputEvent>> inEvents;
|
||||
KeyEvent keyEvent{ TRUE, // keydown
|
||||
1, // repeatCount
|
||||
0, // virtualKeyCode
|
||||
0, // virtualScanCode
|
||||
0, // charData
|
||||
dwControlKeyState }; // activeModifierKeys
|
||||
InputEventQueue inEvents;
|
||||
auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, 0, dwControlKeyState);
|
||||
|
||||
for (const auto& ch : text)
|
||||
{
|
||||
keyEvent.SetCharData(ch);
|
||||
inEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
|
||||
keyEvent.Event.KeyEvent.uChar.UnicodeChar = ch;
|
||||
inEvents.push_back(keyEvent);
|
||||
}
|
||||
|
||||
gci.pInputBuffer->Write(inEvents);
|
||||
|
||||
@ -101,7 +101,7 @@ using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
// Return Value:
|
||||
// - HRESULT indicating success or failure
|
||||
[[nodiscard]] static HRESULT _WriteConsoleInputWImplHelper(InputBuffer& context,
|
||||
std::deque<std::unique_ptr<IInputEvent>>& events,
|
||||
const std::span<const INPUT_RECORD>& events,
|
||||
size_t& written,
|
||||
const bool append) noexcept
|
||||
{
|
||||
@ -151,7 +151,7 @@ try
|
||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
til::small_vector<INPUT_RECORD, 16> events;
|
||||
InputEventQueue events;
|
||||
|
||||
auto it = buffer.begin();
|
||||
const auto end = buffer.end();
|
||||
@ -160,7 +160,7 @@ try
|
||||
// the next call to WriteConsoleInputAImpl to join it with the now available trailing DBCS.
|
||||
if (context.IsWritePartialByteSequenceAvailable())
|
||||
{
|
||||
auto lead = context.FetchWritePartialByteSequence(false)->ToInputRecord();
|
||||
auto lead = context.FetchWritePartialByteSequence();
|
||||
const auto& trail = *it;
|
||||
|
||||
if (trail.EventType == KEY_EVENT)
|
||||
@ -200,7 +200,7 @@ try
|
||||
if (it == end)
|
||||
{
|
||||
// Missing trailing DBCS -> Store the lead for the next call to WriteConsoleInputAImpl.
|
||||
context.StoreWritePartialByteSequence(IInputEvent::Create(lead));
|
||||
context.StoreWritePartialByteSequence(lead);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -225,8 +225,7 @@ try
|
||||
}
|
||||
}
|
||||
|
||||
auto result = IInputEvent::Create(std::span{ events.data(), events.size() });
|
||||
return _WriteConsoleInputWImplHelper(context, result, written, append);
|
||||
return _WriteConsoleInputWImplHelper(context, events, written, append);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
@ -252,9 +251,7 @@ CATCH_RETURN();
|
||||
|
||||
try
|
||||
{
|
||||
auto events = IInputEvent::Create(buffer);
|
||||
|
||||
return _WriteConsoleInputWImplHelper(context, events, written, append);
|
||||
return _WriteConsoleInputWImplHelper(context, buffer, written, append);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
@ -1919,11 +1919,11 @@ void DbcsTests::TestMultibyteInputCoalescing()
|
||||
|
||||
DWORD count;
|
||||
{
|
||||
const auto record = KeyEvent{ true, 1, 123, 456, 0x82, 789 }.ToInputRecord();
|
||||
const auto record = SynthesizeKeyEvent(true, 1, 123, 456, 0x82, 789);
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputA(in, &record, 1, &count));
|
||||
}
|
||||
{
|
||||
const auto record = KeyEvent{ true, 1, 234, 567, 0xA2, 890 }.ToInputRecord();
|
||||
const auto record = SynthesizeKeyEvent(true, 1, 234, 567, 0xA2, 890);
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputA(in, &record, 1, &count));
|
||||
}
|
||||
|
||||
@ -1933,7 +1933,7 @@ void DbcsTests::TestMultibyteInputCoalescing()
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInputW(in, &actual[0], 2, &count));
|
||||
VERIFY_ARE_EQUAL(1u, count);
|
||||
|
||||
const auto expected = KeyEvent{ true, 1, 123, 456, L'い', 789 }.ToInputRecord();
|
||||
const auto expected = SynthesizeKeyEvent(true, 1, 123, 456, L'い', 789);
|
||||
VERIFY_ARE_EQUAL(expected, actual[0]);
|
||||
}
|
||||
|
||||
|
||||
@ -86,7 +86,6 @@ class KeyPressTests
|
||||
expectedRecord.Event.KeyEvent.uChar.UnicodeChar = 0x0;
|
||||
expectedRecord.Event.KeyEvent.bKeyDown = true;
|
||||
expectedRecord.Event.KeyEvent.dwControlKeyState = ENHANCED_KEY;
|
||||
expectedRecord.Event.KeyEvent.dwControlKeyState |= (GetKeyState(VK_NUMLOCK) & KEY_STATE_TOGGLED) ? NUMLOCK_ON : 0;
|
||||
expectedRecord.Event.KeyEvent.wRepeatCount = SINGLE_KEY_REPEAT;
|
||||
expectedRecord.Event.KeyEvent.wVirtualKeyCode = VK_APPS;
|
||||
expectedRecord.Event.KeyEvent.wVirtualScanCode = (WORD)scanCode;
|
||||
|
||||
@ -105,17 +105,18 @@ bool ShouldTakeOverKeyboardShortcuts()
|
||||
|
||||
// Routine Description:
|
||||
// - handles key events without reference to Win32 elements.
|
||||
void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak)
|
||||
void HandleGenericKeyEvent(INPUT_RECORD event, const bool generateBreak)
|
||||
{
|
||||
auto& keyEvent = event.Event.KeyEvent;
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto ContinueProcessing = true;
|
||||
|
||||
if (keyEvent.IsCtrlPressed() &&
|
||||
!keyEvent.IsAltPressed() &&
|
||||
keyEvent.IsKeyDown())
|
||||
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED) &&
|
||||
WI_AreAllFlagsClear(keyEvent.dwControlKeyState, ALT_PRESSED) &&
|
||||
keyEvent.bKeyDown)
|
||||
{
|
||||
// check for ctrl-c, if in line input mode.
|
||||
if (keyEvent.GetVirtualKeyCode() == 'C' && IsInProcessedInputMode())
|
||||
if (keyEvent.wVirtualKeyCode == 'C' && IsInProcessedInputMode())
|
||||
{
|
||||
HandleCtrlEvent(CTRL_C_EVENT);
|
||||
if (gci.PopupCount == 0)
|
||||
@ -130,7 +131,7 @@ void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak)
|
||||
}
|
||||
|
||||
// Check for ctrl-break.
|
||||
else if (keyEvent.GetVirtualKeyCode() == VK_CANCEL)
|
||||
else if (keyEvent.wVirtualKeyCode == VK_CANCEL)
|
||||
{
|
||||
gci.pInputBuffer->Flush();
|
||||
HandleCtrlEvent(CTRL_BREAK_EVENT);
|
||||
@ -146,33 +147,25 @@ void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak)
|
||||
}
|
||||
|
||||
// don't write ctrl-esc to the input buffer
|
||||
else if (keyEvent.GetVirtualKeyCode() == VK_ESCAPE)
|
||||
else if (keyEvent.wVirtualKeyCode == VK_ESCAPE)
|
||||
{
|
||||
ContinueProcessing = false;
|
||||
}
|
||||
}
|
||||
else if (keyEvent.IsAltPressed() &&
|
||||
keyEvent.IsKeyDown() &&
|
||||
keyEvent.GetVirtualKeyCode() == VK_ESCAPE)
|
||||
else if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) &&
|
||||
keyEvent.bKeyDown &&
|
||||
keyEvent.wVirtualKeyCode == VK_ESCAPE)
|
||||
{
|
||||
ContinueProcessing = false;
|
||||
}
|
||||
|
||||
if (ContinueProcessing)
|
||||
{
|
||||
size_t EventsWritten = 0;
|
||||
try
|
||||
gci.pInputBuffer->Write(event);
|
||||
if (generateBreak)
|
||||
{
|
||||
EventsWritten = gci.pInputBuffer->Write(std::make_unique<KeyEvent>(keyEvent));
|
||||
if (EventsWritten && generateBreak)
|
||||
{
|
||||
keyEvent.SetKeyDown(false);
|
||||
EventsWritten = gci.pInputBuffer->Write(std::make_unique<KeyEvent>(keyEvent));
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
keyEvent.bKeyDown = false;
|
||||
gci.pInputBuffer->Write(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -210,7 +203,7 @@ void HandleMenuEvent(const DWORD wParam)
|
||||
size_t EventsWritten = 0;
|
||||
try
|
||||
{
|
||||
EventsWritten = gci.pInputBuffer->Write(std::make_unique<MenuEvent>(wParam));
|
||||
EventsWritten = gci.pInputBuffer->Write(SynthesizeMenuEvent(wParam));
|
||||
if (EventsWritten != 1)
|
||||
{
|
||||
RIPMSG0(RIP_WARNING, "PutInputInBuffer: EventsWritten != 1, 1 expected");
|
||||
|
||||
@ -78,7 +78,7 @@ bool ShouldTakeOverKeyboardShortcuts();
|
||||
void HandleMenuEvent(const DWORD wParam);
|
||||
void HandleFocusEvent(const BOOL fSetFocus);
|
||||
void HandleCtrlEvent(const DWORD EventType);
|
||||
void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak);
|
||||
void HandleGenericKeyEvent(INPUT_RECORD event, const bool generateBreak);
|
||||
|
||||
void ProcessCtrlEvents();
|
||||
|
||||
|
||||
@ -183,7 +183,7 @@ size_t InputBuffer::PeekCached(bool isUnicode, size_t count, InputEventQueue& ta
|
||||
break;
|
||||
}
|
||||
|
||||
target.push_back(IInputEvent::Create(e->ToInputRecord()));
|
||||
target.push_back(e);
|
||||
i++;
|
||||
}
|
||||
|
||||
@ -222,7 +222,7 @@ void InputBuffer::_switchReadingModeSlowPath(ReadingMode mode)
|
||||
_cachedTextW = std::wstring{};
|
||||
_cachedTextReaderW = {};
|
||||
|
||||
_cachedInputEvents = std::deque<std::unique_ptr<IInputEvent>>{};
|
||||
_cachedInputEvents = std::deque<INPUT_RECORD>{};
|
||||
|
||||
_readingMode = mode;
|
||||
}
|
||||
@ -234,9 +234,9 @@ void InputBuffer::_switchReadingModeSlowPath(ReadingMode mode)
|
||||
// - None
|
||||
// Return Value:
|
||||
// - true if partial char data is available, false otherwise
|
||||
bool InputBuffer::IsWritePartialByteSequenceAvailable()
|
||||
bool InputBuffer::IsWritePartialByteSequenceAvailable() const noexcept
|
||||
{
|
||||
return _writePartialByteSequence.get() != nullptr;
|
||||
return _writePartialByteSequenceAvailable;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@ -245,23 +245,10 @@ bool InputBuffer::IsWritePartialByteSequenceAvailable()
|
||||
// - peek - if true, data will not be removed after being fetched
|
||||
// Return Value:
|
||||
// - the partial char data. may be nullptr if no data is available
|
||||
std::unique_ptr<IInputEvent> InputBuffer::FetchWritePartialByteSequence(_In_ bool peek)
|
||||
const INPUT_RECORD& InputBuffer::FetchWritePartialByteSequence() noexcept
|
||||
{
|
||||
if (!IsWritePartialByteSequenceAvailable())
|
||||
{
|
||||
return std::unique_ptr<IInputEvent>();
|
||||
}
|
||||
|
||||
if (peek)
|
||||
{
|
||||
return IInputEvent::Create(_writePartialByteSequence->ToInputRecord());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_ptr<IInputEvent> outEvent;
|
||||
outEvent.swap(_writePartialByteSequence);
|
||||
return outEvent;
|
||||
}
|
||||
_writePartialByteSequenceAvailable = false;
|
||||
return _writePartialByteSequence;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@ -271,9 +258,10 @@ std::unique_ptr<IInputEvent> InputBuffer::FetchWritePartialByteSequence(_In_ boo
|
||||
// - event - The event to store
|
||||
// Return Value:
|
||||
// - None
|
||||
void InputBuffer::StoreWritePartialByteSequence(std::unique_ptr<IInputEvent> event)
|
||||
void InputBuffer::StoreWritePartialByteSequence(const INPUT_RECORD& event) noexcept
|
||||
{
|
||||
_writePartialByteSequence.swap(event);
|
||||
_writePartialByteSequenceAvailable = true;
|
||||
_writePartialByteSequence = event;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@ -348,8 +336,8 @@ void InputBuffer::Flush()
|
||||
// - The console lock must be held when calling this routine.
|
||||
void InputBuffer::FlushAllButKeys()
|
||||
{
|
||||
auto newEnd = std::remove_if(_storage.begin(), _storage.end(), [](const std::unique_ptr<IInputEvent>& event) {
|
||||
return event->EventType() != InputEventType::KeyEvent;
|
||||
auto newEnd = std::remove_if(_storage.begin(), _storage.end(), [](const INPUT_RECORD& event) {
|
||||
return event.EventType != KEY_EVENT;
|
||||
});
|
||||
_storage.erase(newEnd, _storage.end());
|
||||
}
|
||||
@ -391,7 +379,7 @@ void InputBuffer::PassThroughWin32MouseRequest(bool enable)
|
||||
// - STATUS_SUCCESS if records were read into the client buffer and everything is OK.
|
||||
// - CONSOLE_STATUS_WAIT if there weren't enough records to satisfy the request (and waits are allowed)
|
||||
// - otherwise a suitable memory/math/string error in NTSTATUS form.
|
||||
[[nodiscard]] NTSTATUS InputBuffer::Read(_Out_ std::deque<std::unique_ptr<IInputEvent>>& OutEvents,
|
||||
[[nodiscard]] NTSTATUS InputBuffer::Read(_Out_ InputEventQueue& OutEvents,
|
||||
const size_t AmountToRead,
|
||||
const bool Peek,
|
||||
const bool WaitForData,
|
||||
@ -417,31 +405,29 @@ try
|
||||
|
||||
while (it != end && OutEvents.size() < AmountToRead)
|
||||
{
|
||||
auto event = IInputEvent::Create((*it)->ToInputRecord());
|
||||
|
||||
if (event->EventType() == InputEventType::KeyEvent)
|
||||
if (it->EventType == KEY_EVENT)
|
||||
{
|
||||
const auto keyEvent = static_cast<KeyEvent*>(event.get());
|
||||
auto event = *it;
|
||||
WORD repeat = 1;
|
||||
|
||||
// for stream reads we need to split any key events that have been coalesced
|
||||
if (Stream)
|
||||
{
|
||||
repeat = keyEvent->GetRepeatCount();
|
||||
keyEvent->SetRepeatCount(1);
|
||||
repeat = std::max<WORD>(1, event.Event.KeyEvent.wRepeatCount);
|
||||
event.Event.KeyEvent.wRepeatCount = 1;
|
||||
}
|
||||
|
||||
if (Unicode)
|
||||
{
|
||||
do
|
||||
{
|
||||
OutEvents.push_back(std::make_unique<KeyEvent>(*keyEvent));
|
||||
OutEvents.push_back(event);
|
||||
repeat--;
|
||||
} while (repeat > 0 && OutEvents.size() < AmountToRead);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto wch = keyEvent->GetCharData();
|
||||
const auto wch = event.Event.KeyEvent.uChar.UnicodeChar;
|
||||
|
||||
char buffer[8];
|
||||
const auto length = WideCharToMultiByte(cp, 0, &wch, 1, &buffer[0], sizeof(buffer), nullptr, nullptr);
|
||||
@ -453,9 +439,10 @@ try
|
||||
{
|
||||
for (const auto& ch : str)
|
||||
{
|
||||
auto tempEvent = std::make_unique<KeyEvent>(*keyEvent);
|
||||
tempEvent->SetCharData(ch);
|
||||
OutEvents.push_back(std::move(tempEvent));
|
||||
// char is signed and assigning it to UnicodeChar would cause sign-extension.
|
||||
// unsigned char doesn't have this problem.
|
||||
event.Event.KeyEvent.uChar.UnicodeChar = til::bit_cast<uint8_t>(ch);
|
||||
OutEvents.push_back(event);
|
||||
}
|
||||
repeat--;
|
||||
} while (repeat > 0 && OutEvents.size() < AmountToRead);
|
||||
@ -463,14 +450,13 @@ try
|
||||
|
||||
if (repeat && !Peek)
|
||||
{
|
||||
const auto originalKeyEvent = static_cast<KeyEvent*>((*it).get());
|
||||
originalKeyEvent->SetRepeatCount(repeat);
|
||||
it->Event.KeyEvent.wRepeatCount = repeat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OutEvents.push_back(std::move(event));
|
||||
OutEvents.push_back(*it);
|
||||
}
|
||||
|
||||
++it;
|
||||
@ -498,51 +484,6 @@ catch (...)
|
||||
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine reads a single event from the input buffer.
|
||||
// - It can convert returned data to through the currently set Input CP, it can optionally return a wait condition
|
||||
// if there isn't enough data in the buffer, and it can be set to not remove records as it reads them out.
|
||||
// Note:
|
||||
// - The console lock must be held when calling this routine.
|
||||
// Arguments:
|
||||
// - outEvent - where the read event is stored
|
||||
// - Peek - If true, copy events to pInputRecord but don't remove them from the input buffer.
|
||||
// - WaitForData - if true, wait until an event is input (if there aren't enough to fill client buffer). if false, return immediately
|
||||
// - Unicode - true if the data in key events should be treated as unicode. false if they should be converted by the current input CP.
|
||||
// - Stream - true if read should unpack KeyEvents that have a >1 repeat count.
|
||||
// Return Value:
|
||||
// - STATUS_SUCCESS if records were read into the client buffer and everything is OK.
|
||||
// - CONSOLE_STATUS_WAIT if there weren't enough records to satisfy the request (and waits are allowed)
|
||||
// - otherwise a suitable memory/math/string error in NTSTATUS form.
|
||||
[[nodiscard]] NTSTATUS InputBuffer::Read(_Out_ std::unique_ptr<IInputEvent>& outEvent,
|
||||
const bool Peek,
|
||||
const bool WaitForData,
|
||||
const bool Unicode,
|
||||
const bool Stream)
|
||||
{
|
||||
NTSTATUS Status;
|
||||
try
|
||||
{
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
Status = Read(outEvents,
|
||||
1,
|
||||
Peek,
|
||||
WaitForData,
|
||||
Unicode,
|
||||
Stream);
|
||||
if (!outEvents.empty())
|
||||
{
|
||||
outEvent.swap(outEvents.front());
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Status = NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes events to the beginning of the input buffer.
|
||||
// Arguments:
|
||||
@ -552,7 +493,7 @@ catch (...)
|
||||
// S_OK if successful.
|
||||
// Note:
|
||||
// - The console lock must be held when calling this routine.
|
||||
size_t InputBuffer::Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
|
||||
size_t InputBuffer::Prepend(const std::span<const INPUT_RECORD>& inEvents)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -569,7 +510,7 @@ size_t InputBuffer::Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& in
|
||||
// this way to handle any coalescing that might occur.
|
||||
|
||||
// get all of the existing records, "emptying" the buffer
|
||||
std::deque<std::unique_ptr<IInputEvent>> existingStorage;
|
||||
std::deque<INPUT_RECORD> existingStorage;
|
||||
existingStorage.swap(_storage);
|
||||
|
||||
// We will need this variable to pass to _WriteBuffer so it can attempt to determine wait status.
|
||||
@ -583,10 +524,10 @@ size_t InputBuffer::Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& in
|
||||
_WriteBuffer(inEvents, prependEventsWritten, unusedWaitStatus);
|
||||
FAIL_FAST_IF(!(unusedWaitStatus));
|
||||
|
||||
// write all previously existing records
|
||||
size_t existingEventsWritten;
|
||||
_WriteBuffer(existingStorage, existingEventsWritten, unusedWaitStatus);
|
||||
FAIL_FAST_IF(!(!unusedWaitStatus));
|
||||
for (const auto& event : existingStorage)
|
||||
{
|
||||
_storage.push_back(event);
|
||||
}
|
||||
|
||||
// We need to set the wait event if there were 0 events in the
|
||||
// input queue when we started.
|
||||
@ -621,19 +562,9 @@ size_t InputBuffer::Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& in
|
||||
// - The console lock must be held when calling this routine.
|
||||
// - any outside references to inEvent will ben invalidated after
|
||||
// calling this method.
|
||||
size_t InputBuffer::Write(_Inout_ std::unique_ptr<IInputEvent> inEvent)
|
||||
size_t InputBuffer::Write(const INPUT_RECORD& inEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::deque<std::unique_ptr<IInputEvent>> inEvents;
|
||||
inEvents.push_back(std::move(inEvent));
|
||||
return Write(inEvents);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
return 0;
|
||||
}
|
||||
return Write(std::span{ &inEvent, 1 });
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@ -645,7 +576,7 @@ size_t InputBuffer::Write(_Inout_ std::unique_ptr<IInputEvent> inEvent)
|
||||
// - The number of events that were written to input buffer.
|
||||
// Note:
|
||||
// - The console lock must be held when calling this routine.
|
||||
size_t InputBuffer::Write(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
|
||||
size_t InputBuffer::Write(const std::span<const INPUT_RECORD>& inEvents)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -694,7 +625,7 @@ void InputBuffer::WriteFocusEvent(bool focused) noexcept
|
||||
{
|
||||
// This is a mini-version of Write().
|
||||
const auto wasEmpty = _storage.empty();
|
||||
_storage.push_back(std::make_unique<FocusEvent>(focused));
|
||||
_storage.push_back(SynthesizeFocusEvent(focused));
|
||||
if (wasEmpty)
|
||||
{
|
||||
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
|
||||
@ -760,9 +691,7 @@ static bool IsPauseKey(const KEY_EVENT_RECORD& event)
|
||||
// Note:
|
||||
// - The console lock must be held when calling this routine.
|
||||
// - will throw on failure
|
||||
void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents,
|
||||
_Out_ size_t& eventsWritten,
|
||||
_Out_ bool& setWaitEvent)
|
||||
void InputBuffer::_WriteBuffer(const std::span<const INPUT_RECORD>& inEvents, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent)
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
@ -772,18 +701,18 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
|
||||
const auto initialInEventsSize = inEvents.size();
|
||||
const auto vtInputMode = IsInVirtualTerminalInputMode();
|
||||
|
||||
for (auto& inEvent : inEvents)
|
||||
for (const auto& inEvent : inEvents)
|
||||
{
|
||||
if (inEvent->EventType() == InputEventType::KeyEvent && static_cast<const KeyEvent*>(inEvent.get())->IsKeyDown())
|
||||
if (inEvent.EventType == KEY_EVENT && inEvent.Event.KeyEvent.bKeyDown)
|
||||
{
|
||||
// if output is suspended, any keyboard input releases it.
|
||||
if (WI_IsFlagSet(gci.Flags, CONSOLE_SUSPENDED) && !IsSystemKey(static_cast<const KeyEvent*>(inEvent.get())->GetVirtualKeyCode()))
|
||||
if (WI_IsFlagSet(gci.Flags, CONSOLE_SUSPENDED) && !IsSystemKey(inEvent.Event.KeyEvent.wVirtualKeyCode))
|
||||
{
|
||||
UnblockWriteConsole(CONSOLE_OUTPUT_SUSPENDED);
|
||||
continue;
|
||||
}
|
||||
// intercept control-s
|
||||
if (WI_IsFlagSet(InputMode, ENABLE_LINE_INPUT) && IsPauseKey(inEvent->ToInputRecord().Event.KeyEvent))
|
||||
if (WI_IsFlagSet(InputMode, ENABLE_LINE_INPUT) && IsPauseKey(inEvent.Event.KeyEvent))
|
||||
{
|
||||
WI_SetFlag(gci.Flags, CONSOLE_SUSPENDED);
|
||||
continue;
|
||||
@ -797,7 +726,7 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
|
||||
if (vtInputMode)
|
||||
{
|
||||
// GH#11682: TerminalInput::HandleKey can handle both KeyEvents and Focus events seamlessly
|
||||
if (const auto out = _termInput.HandleKey(inEvent.get()))
|
||||
if (const auto out = _termInput.HandleKey(inEvent))
|
||||
{
|
||||
_HandleTerminalInputCallback(*out);
|
||||
eventsWritten++;
|
||||
@ -816,7 +745,7 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
|
||||
}
|
||||
|
||||
// At this point, the event was neither coalesced, nor processed by VT.
|
||||
_storage.push_back(std::move(inEvent));
|
||||
_storage.push_back(inEvent);
|
||||
++eventsWritten;
|
||||
}
|
||||
if (initiallyEmptyQueue && !_storage.empty())
|
||||
@ -839,39 +768,39 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
|
||||
// the buffer with updated values from an incoming event, instead of
|
||||
// storing the incoming event (which would make the original one
|
||||
// redundant/out of date with the most current state).
|
||||
bool InputBuffer::_CoalesceEvent(const std::unique_ptr<IInputEvent>& inEvent) const noexcept
|
||||
bool InputBuffer::_CoalesceEvent(const INPUT_RECORD& inEvent) noexcept
|
||||
{
|
||||
auto& lastEvent = _storage.back();
|
||||
|
||||
if (lastEvent->EventType() == InputEventType::MouseEvent && inEvent->EventType() == InputEventType::MouseEvent)
|
||||
if (lastEvent.EventType == MOUSE_EVENT && inEvent.EventType == MOUSE_EVENT)
|
||||
{
|
||||
const auto& inMouse = *static_cast<const MouseEvent*>(inEvent.get());
|
||||
auto& lastMouse = *static_cast<MouseEvent*>(lastEvent.get());
|
||||
const auto& inMouse = inEvent.Event.MouseEvent;
|
||||
auto& lastMouse = lastEvent.Event.MouseEvent;
|
||||
|
||||
if (lastMouse.IsMouseMoveEvent() && inMouse.IsMouseMoveEvent())
|
||||
if (lastMouse.dwEventFlags == MOUSE_MOVED && inMouse.dwEventFlags == MOUSE_MOVED)
|
||||
{
|
||||
lastMouse.SetPosition(inMouse.GetPosition());
|
||||
lastMouse.dwMousePosition = inMouse.dwMousePosition;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (lastEvent->EventType() == InputEventType::KeyEvent && inEvent->EventType() == InputEventType::KeyEvent)
|
||||
else if (lastEvent.EventType == KEY_EVENT && inEvent.EventType == KEY_EVENT)
|
||||
{
|
||||
const auto& inKey = *static_cast<const KeyEvent*>(inEvent.get());
|
||||
auto& lastKey = *static_cast<KeyEvent*>(lastEvent.get());
|
||||
const auto& inKey = inEvent.Event.KeyEvent;
|
||||
auto& lastKey = lastEvent.Event.KeyEvent;
|
||||
|
||||
if (lastKey.IsKeyDown() && inKey.IsKeyDown() &&
|
||||
(lastKey.GetVirtualScanCode() == inKey.GetVirtualScanCode() || WI_IsFlagSet(inKey.GetActiveModifierKeys(), NLS_IME_CONVERSION)) &&
|
||||
lastKey.GetCharData() == inKey.GetCharData() &&
|
||||
lastKey.GetActiveModifierKeys() == inKey.GetActiveModifierKeys() &&
|
||||
// TODO: This behavior is an import from old conhost v1 and has been broken for decades.
|
||||
if (lastKey.bKeyDown && inKey.bKeyDown &&
|
||||
(lastKey.wVirtualScanCode == inKey.wVirtualScanCode || WI_IsFlagSet(inKey.dwControlKeyState, NLS_IME_CONVERSION)) &&
|
||||
lastKey.uChar.UnicodeChar == inKey.uChar.UnicodeChar &&
|
||||
lastKey.dwControlKeyState == inKey.dwControlKeyState &&
|
||||
// TODO:GH#8000 This behavior is an import from old conhost v1 and has been broken for decades.
|
||||
// This is probably the outdated idea that any wide glyph is being represented by 2 characters (DBCS) and likely
|
||||
// resulted from conhost originally being split into a ASCII/OEM and a DBCS variant with preprocessor flags.
|
||||
// You can't update the repeat count of such a A,B pair, because they're stored as A,A,B,B (down-down, up-up).
|
||||
// I believe the proper approach is to store pairs of characters as pairs, update their combined
|
||||
// repeat count and only when they're being read de-coalesce them into their alternating form.
|
||||
!IsGlyphFullWidth(inKey.GetCharData()))
|
||||
!IsGlyphFullWidth(inKey.uChar.UnicodeChar))
|
||||
{
|
||||
lastKey.SetRepeatCount(lastKey.GetRepeatCount() + inKey.GetRepeatCount());
|
||||
lastKey.wRepeatCount += inKey.wRepeatCount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -908,7 +837,7 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType&
|
||||
|
||||
for (const auto& wch : text)
|
||||
{
|
||||
_storage.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
|
||||
_storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
|
||||
}
|
||||
|
||||
if (!_vtInputShouldSuppress)
|
||||
|
||||
@ -1,20 +1,5 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- inputBuffer.hpp
|
||||
|
||||
Abstract:
|
||||
- storage area for incoming input events.
|
||||
|
||||
Author:
|
||||
- Therese Stowell (Thereses) 12-Nov-1990. Adapted from OS/2 subsystem server\srvpipe.c
|
||||
|
||||
Revision History:
|
||||
- Moved from input.h/input.cpp. (AustDi, 2017)
|
||||
- Refactored to class, added stl container usage (AustDi, 2017)
|
||||
--*/
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -52,9 +37,9 @@ public:
|
||||
void Cache(bool isUnicode, InputEventQueue& source, size_t expectedSourceSize);
|
||||
|
||||
// storage API for partial dbcs bytes being written to the buffer
|
||||
bool IsWritePartialByteSequenceAvailable();
|
||||
std::unique_ptr<IInputEvent> FetchWritePartialByteSequence(_In_ bool peek);
|
||||
void StoreWritePartialByteSequence(std::unique_ptr<IInputEvent> event);
|
||||
bool IsWritePartialByteSequenceAvailable() const noexcept;
|
||||
const INPUT_RECORD& FetchWritePartialByteSequence() noexcept;
|
||||
void StoreWritePartialByteSequence(const INPUT_RECORD& event) noexcept;
|
||||
|
||||
void ReinitializeInputBuffer();
|
||||
void WakeUpReadersWaitingForData();
|
||||
@ -63,24 +48,16 @@ public:
|
||||
void Flush();
|
||||
void FlushAllButKeys();
|
||||
|
||||
[[nodiscard]] NTSTATUS Read(_Out_ std::deque<std::unique_ptr<IInputEvent>>& OutEvents,
|
||||
[[nodiscard]] NTSTATUS Read(_Out_ InputEventQueue& OutEvents,
|
||||
const size_t AmountToRead,
|
||||
const bool Peek,
|
||||
const bool WaitForData,
|
||||
const bool Unicode,
|
||||
const bool Stream);
|
||||
|
||||
[[nodiscard]] NTSTATUS Read(_Out_ std::unique_ptr<IInputEvent>& inEvent,
|
||||
const bool Peek,
|
||||
const bool WaitForData,
|
||||
const bool Unicode,
|
||||
const bool Stream);
|
||||
|
||||
size_t Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents);
|
||||
|
||||
size_t Write(_Inout_ std::unique_ptr<IInputEvent> inEvent);
|
||||
size_t Write(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents);
|
||||
|
||||
size_t Prepend(const std::span<const INPUT_RECORD>& inEvents);
|
||||
size_t Write(const INPUT_RECORD& inEvent);
|
||||
size_t Write(const std::span<const INPUT_RECORD>& inEvents);
|
||||
void WriteFocusEvent(bool focused) noexcept;
|
||||
bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta);
|
||||
|
||||
@ -102,11 +79,12 @@ private:
|
||||
std::string_view _cachedTextReaderA;
|
||||
std::wstring _cachedTextW;
|
||||
std::wstring_view _cachedTextReaderW;
|
||||
std::deque<std::unique_ptr<IInputEvent>> _cachedInputEvents;
|
||||
std::deque<INPUT_RECORD> _cachedInputEvents;
|
||||
ReadingMode _readingMode = ReadingMode::StringA;
|
||||
|
||||
std::deque<std::unique_ptr<IInputEvent>> _storage;
|
||||
std::unique_ptr<IInputEvent> _writePartialByteSequence;
|
||||
std::deque<INPUT_RECORD> _storage;
|
||||
INPUT_RECORD _writePartialByteSequence{};
|
||||
bool _writePartialByteSequenceAvailable = false;
|
||||
Microsoft::Console::VirtualTerminal::TerminalInput _termInput;
|
||||
Microsoft::Console::Render::VtEngine* _pTtyConnection;
|
||||
|
||||
@ -118,12 +96,8 @@ private:
|
||||
|
||||
void _switchReadingMode(ReadingMode mode);
|
||||
void _switchReadingModeSlowPath(ReadingMode mode);
|
||||
|
||||
void _WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inRecords,
|
||||
_Out_ size_t& eventsWritten,
|
||||
_Out_ bool& setWaitEvent);
|
||||
|
||||
bool _CoalesceEvent(const std::unique_ptr<IInputEvent>& inEvent) const noexcept;
|
||||
void _WriteBuffer(const std::span<const INPUT_RECORD>& inRecords, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent);
|
||||
bool _CoalesceEvent(const INPUT_RECORD& inEvent) noexcept;
|
||||
void _HandleTerminalInputCallback(const Microsoft::Console::VirtualTerminal::TerminalInput::StringType& text);
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
|
||||
@ -257,7 +257,7 @@ void ScreenBufferSizeChange(const til::size coordNewSize)
|
||||
|
||||
try
|
||||
{
|
||||
gci.pInputBuffer->Write(std::make_unique<WindowBufferSizeEvent>(coordNewSize));
|
||||
gci.pInputBuffer->Write(SynthesizeWindowBufferSizeEvent(coordNewSize));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
||||
@ -33,18 +33,17 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) :
|
||||
// - <none>
|
||||
void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response)
|
||||
{
|
||||
std::deque<std::unique_ptr<IInputEvent>> inEvents;
|
||||
InputEventQueue inEvents;
|
||||
|
||||
// generate a paired key down and key up event for every
|
||||
// character to be sent into the console's input buffer
|
||||
for (const auto& wch : response)
|
||||
{
|
||||
// This wasn't from a real keyboard, so we're leaving key/scan codes blank.
|
||||
KeyEvent keyEvent{ TRUE, 1, 0, 0, wch, 0 };
|
||||
|
||||
inEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
|
||||
keyEvent.SetKeyDown(false);
|
||||
inEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
|
||||
auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, wch, 0);
|
||||
inEvents.push_back(keyEvent);
|
||||
keyEvent.Event.KeyEvent.bKeyDown = false;
|
||||
inEvents.push_back(keyEvent);
|
||||
}
|
||||
|
||||
// TODO GH#4954 During the input refactor we may want to add a "priority" input list
|
||||
|
||||
@ -47,7 +47,7 @@ DirectReadData::DirectReadData(_In_ InputBuffer* const pInputBuffer,
|
||||
// - pControlKeyState - For certain types of reads, this specifies
|
||||
// which modifier keys were held.
|
||||
// - pOutputData - a pointer to a
|
||||
// std::deque<std::unique_ptr<IInputEvent>> that is used to the read
|
||||
// InputEventQueue that is used to the read
|
||||
// input events back to the server
|
||||
// Return Value:
|
||||
// - true if the wait is done and result buffer/status code can be sent back to the client.
|
||||
@ -69,8 +69,6 @@ try
|
||||
*pControlKeyState = 0;
|
||||
*pNumBytes = 0;
|
||||
|
||||
std::deque<std::unique_ptr<IInputEvent>> readEvents;
|
||||
|
||||
// If ctrl-c or ctrl-break was seen, ignore it.
|
||||
if (WI_IsAnyFlagSet(TerminationReason, (WaitTerminationReason::CtrlC | WaitTerminationReason::CtrlBreak)))
|
||||
{
|
||||
@ -119,7 +117,7 @@ try
|
||||
}
|
||||
|
||||
// move events to pOutputData
|
||||
const auto pOutputDeque = static_cast<std::deque<std::unique_ptr<IInputEvent>>* const>(pOutputData);
|
||||
const auto pOutputDeque = static_cast<InputEventQueue* const>(pOutputData);
|
||||
*pNumBytes = _outEvents.size() * sizeof(INPUT_RECORD);
|
||||
*pOutputDeque = std::move(_outEvents);
|
||||
|
||||
|
||||
@ -47,5 +47,5 @@ public:
|
||||
|
||||
private:
|
||||
const size_t _eventReadCount;
|
||||
std::deque<std::unique_ptr<IInputEvent>> _outEvents;
|
||||
InputEventQueue _outEvents;
|
||||
};
|
||||
|
||||
@ -16,10 +16,95 @@
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
|
||||
static bool IsCommandLinePopupKey(const KEY_EVENT_RECORD& event)
|
||||
{
|
||||
if (WI_AreAllFlagsClear(event.dwControlKeyState, RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
|
||||
{
|
||||
switch (event.wVirtualKeyCode)
|
||||
{
|
||||
case VK_ESCAPE:
|
||||
case VK_PRIOR:
|
||||
case VK_NEXT:
|
||||
case VK_END:
|
||||
case VK_HOME:
|
||||
case VK_LEFT:
|
||||
case VK_UP:
|
||||
case VK_RIGHT:
|
||||
case VK_DOWN:
|
||||
case VK_F2:
|
||||
case VK_F4:
|
||||
case VK_F7:
|
||||
case VK_F9:
|
||||
case VK_DELETE:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsCommandLineEditingKey(const KEY_EVENT_RECORD& event)
|
||||
{
|
||||
if (WI_AreAllFlagsClear(event.dwControlKeyState, RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
|
||||
{
|
||||
switch (event.wVirtualKeyCode)
|
||||
{
|
||||
case VK_ESCAPE:
|
||||
case VK_PRIOR:
|
||||
case VK_NEXT:
|
||||
case VK_END:
|
||||
case VK_HOME:
|
||||
case VK_LEFT:
|
||||
case VK_UP:
|
||||
case VK_RIGHT:
|
||||
case VK_DOWN:
|
||||
case VK_INSERT:
|
||||
case VK_DELETE:
|
||||
case VK_F1:
|
||||
case VK_F2:
|
||||
case VK_F3:
|
||||
case VK_F4:
|
||||
case VK_F5:
|
||||
case VK_F6:
|
||||
case VK_F7:
|
||||
case VK_F8:
|
||||
case VK_F9:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (WI_IsAnyFlagSet(event.dwControlKeyState, RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
|
||||
{
|
||||
switch (event.wVirtualKeyCode)
|
||||
{
|
||||
case VK_END:
|
||||
case VK_HOME:
|
||||
case VK_LEFT:
|
||||
case VK_RIGHT:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (WI_IsAnyFlagSet(event.dwControlKeyState, RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED))
|
||||
{
|
||||
switch (event.wVirtualKeyCode)
|
||||
{
|
||||
case VK_F7:
|
||||
case VK_F10:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine is used in stream input. It gets input and filters it for unicode characters.
|
||||
// Arguments:
|
||||
@ -56,57 +141,50 @@ using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
*pdwKeyState = 0;
|
||||
}
|
||||
|
||||
NTSTATUS Status;
|
||||
for (;;)
|
||||
{
|
||||
std::unique_ptr<IInputEvent> inputEvent;
|
||||
Status = pInputBuffer->Read(inputEvent,
|
||||
false, // peek
|
||||
Wait,
|
||||
true, // unicode
|
||||
true); // stream
|
||||
|
||||
InputEventQueue events;
|
||||
const auto Status = pInputBuffer->Read(events, 1, false, Wait, true, true);
|
||||
if (FAILED_NTSTATUS(Status))
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
else if (inputEvent.get() == nullptr)
|
||||
if (events.empty())
|
||||
{
|
||||
FAIL_FAST_IF(Wait);
|
||||
assert(!Wait);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
if (inputEvent->EventType() == InputEventType::KeyEvent)
|
||||
const auto& Event = events[0];
|
||||
if (Event.EventType == KEY_EVENT)
|
||||
{
|
||||
auto keyEvent = std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(inputEvent.release()));
|
||||
|
||||
auto commandLineEditKey = false;
|
||||
if (pCommandLineEditingKeys)
|
||||
{
|
||||
commandLineEditKey = keyEvent->IsCommandLineEditingKey();
|
||||
commandLineEditKey = IsCommandLineEditingKey(Event.Event.KeyEvent);
|
||||
}
|
||||
else if (pPopupKeys)
|
||||
{
|
||||
commandLineEditKey = keyEvent->IsPopupKey();
|
||||
commandLineEditKey = IsCommandLinePopupKey(Event.Event.KeyEvent);
|
||||
}
|
||||
|
||||
if (pdwKeyState)
|
||||
{
|
||||
*pdwKeyState = keyEvent->GetActiveModifierKeys();
|
||||
*pdwKeyState = Event.Event.KeyEvent.dwControlKeyState;
|
||||
}
|
||||
|
||||
if (keyEvent->GetCharData() != 0 && !commandLineEditKey)
|
||||
if (Event.Event.KeyEvent.uChar.UnicodeChar != 0 && !commandLineEditKey)
|
||||
{
|
||||
// chars that are generated using alt + numpad
|
||||
if (!keyEvent->IsKeyDown() && keyEvent->GetVirtualKeyCode() == VK_MENU)
|
||||
if (!Event.Event.KeyEvent.bKeyDown && Event.Event.KeyEvent.wVirtualKeyCode == VK_MENU)
|
||||
{
|
||||
if (keyEvent->IsAltNumpadSet())
|
||||
if (WI_IsFlagSet(Event.Event.KeyEvent.dwControlKeyState, ALTNUMPAD_BIT))
|
||||
{
|
||||
if (HIBYTE(keyEvent->GetCharData()))
|
||||
if (HIBYTE(Event.Event.KeyEvent.uChar.UnicodeChar))
|
||||
{
|
||||
char chT[2] = {
|
||||
static_cast<char>(HIBYTE(keyEvent->GetCharData())),
|
||||
static_cast<char>(LOBYTE(keyEvent->GetCharData())),
|
||||
const char chT[2] = {
|
||||
static_cast<char>(HIBYTE(Event.Event.KeyEvent.uChar.UnicodeChar)),
|
||||
static_cast<char>(LOBYTE(Event.Event.KeyEvent.uChar.UnicodeChar)),
|
||||
};
|
||||
*pwchOut = CharToWchar(chT, 2);
|
||||
}
|
||||
@ -115,64 +193,54 @@ using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
// Because USER doesn't know our codepage,
|
||||
// it gives us the raw OEM char and we
|
||||
// convert it to a Unicode character.
|
||||
char chT = LOBYTE(keyEvent->GetCharData());
|
||||
char chT = LOBYTE(Event.Event.KeyEvent.uChar.UnicodeChar);
|
||||
*pwchOut = CharToWchar(&chT, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*pwchOut = keyEvent->GetCharData();
|
||||
*pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Ignore Escape and Newline chars
|
||||
else if (keyEvent->IsKeyDown() &&
|
||||
(WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT) ||
|
||||
(keyEvent->GetVirtualKeyCode() != VK_ESCAPE &&
|
||||
keyEvent->GetCharData() != UNICODE_LINEFEED)))
|
||||
if (Event.Event.KeyEvent.bKeyDown &&
|
||||
(WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT) ||
|
||||
(Event.Event.KeyEvent.wVirtualKeyCode != VK_ESCAPE &&
|
||||
Event.Event.KeyEvent.uChar.UnicodeChar != UNICODE_LINEFEED)))
|
||||
{
|
||||
*pwchOut = keyEvent->GetCharData();
|
||||
*pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyEvent->IsKeyDown())
|
||||
if (Event.Event.KeyEvent.bKeyDown)
|
||||
{
|
||||
if (pCommandLineEditingKeys && commandLineEditKey)
|
||||
{
|
||||
*pCommandLineEditingKeys = true;
|
||||
*pwchOut = static_cast<wchar_t>(keyEvent->GetVirtualKeyCode());
|
||||
*pwchOut = static_cast<wchar_t>(Event.Event.KeyEvent.wVirtualKeyCode);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
else if (pPopupKeys && commandLineEditKey)
|
||||
|
||||
if (pPopupKeys && commandLineEditKey)
|
||||
{
|
||||
*pPopupKeys = true;
|
||||
*pwchOut = static_cast<char>(keyEvent->GetVirtualKeyCode());
|
||||
*pwchOut = static_cast<char>(Event.Event.KeyEvent.wVirtualKeyCode);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
else
|
||||
|
||||
const auto zeroKey = OneCoreSafeVkKeyScanW(0);
|
||||
|
||||
if (LOBYTE(zeroKey) == Event.Event.KeyEvent.wVirtualKeyCode &&
|
||||
WI_IsAnyFlagSet(Event.Event.KeyEvent.dwControlKeyState, ALT_PRESSED) == WI_IsFlagSet(zeroKey, 0x400) &&
|
||||
WI_IsAnyFlagSet(Event.Event.KeyEvent.dwControlKeyState, CTRL_PRESSED) == WI_IsFlagSet(zeroKey, 0x200) &&
|
||||
WI_IsAnyFlagSet(Event.Event.KeyEvent.dwControlKeyState, SHIFT_PRESSED) == WI_IsFlagSet(zeroKey, 0x100))
|
||||
{
|
||||
const auto zeroVkeyData = OneCoreSafeVkKeyScanW(0);
|
||||
const auto zeroVKey = LOBYTE(zeroVkeyData);
|
||||
const auto zeroControlKeyState = HIBYTE(zeroVkeyData);
|
||||
|
||||
try
|
||||
{
|
||||
// Convert real Windows NT modifier bit into bizarre Console bits
|
||||
auto consoleModKeyState = FromVkKeyScan(zeroControlKeyState);
|
||||
|
||||
if (zeroVKey == keyEvent->GetVirtualKeyCode() &&
|
||||
keyEvent->DoActiveModifierKeysMatch(consoleModKeyState))
|
||||
{
|
||||
// This really is the character 0x0000
|
||||
*pwchOut = keyEvent->GetCharData();
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
}
|
||||
// This really is the character 0x0000
|
||||
*pwchOut = Event.Event.KeyEvent.uChar.UnicodeChar;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,106 +153,45 @@ class ClipboardTests
|
||||
VERIFY_IS_NOT_NULL(ptr);
|
||||
}
|
||||
|
||||
TEST_METHOD(CanConvertTextToInputEvents)
|
||||
TEST_METHOD(CanConvertText)
|
||||
{
|
||||
std::wstring wstr = L"hello world";
|
||||
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
|
||||
wstr.size());
|
||||
VERIFY_ARE_EQUAL(wstr.size() * 2, events.size());
|
||||
for (auto wch : wstr)
|
||||
static constexpr std::wstring_view input{ L"HeLlO WoRlD" };
|
||||
const auto events = Clipboard::Instance().TextToKeyEvents(input.data(), input.size());
|
||||
|
||||
const auto shiftSC = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_SHIFT, MAPVK_VK_TO_VSC));
|
||||
const auto shiftDown = SynthesizeKeyEvent(true, 1, VK_SHIFT, shiftSC, 0, SHIFT_PRESSED);
|
||||
const auto shiftUp = SynthesizeKeyEvent(false, 1, VK_SHIFT, shiftSC, 0, 0);
|
||||
|
||||
InputEventQueue expectedEvents;
|
||||
|
||||
for (auto wch : input)
|
||||
{
|
||||
std::deque<bool> keydownPattern{ true, false };
|
||||
for (auto isKeyDown : keydownPattern)
|
||||
const auto state = OneCoreSafeVkKeyScanW(wch);
|
||||
const auto vk = LOBYTE(state);
|
||||
const auto sc = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
|
||||
const auto shift = WI_IsFlagSet(state, 0x100);
|
||||
auto event = SynthesizeKeyEvent(true, 1, vk, sc, wch, shift ? SHIFT_PRESSED : 0);
|
||||
|
||||
if (shift)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(InputEventType::KeyEvent, events.front()->EventType());
|
||||
std::unique_ptr<KeyEvent> keyEvent;
|
||||
keyEvent.reset(static_cast<KeyEvent* const>(events.front().release()));
|
||||
events.pop_front();
|
||||
expectedEvents.push_back(shiftDown);
|
||||
}
|
||||
|
||||
const auto keyState = OneCoreSafeVkKeyScanW(wch);
|
||||
VERIFY_ARE_NOT_EQUAL(-1, keyState);
|
||||
const auto virtualScanCode = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(LOBYTE(keyState), MAPVK_VK_TO_VSC));
|
||||
expectedEvents.push_back(event);
|
||||
event.Event.KeyEvent.bKeyDown = FALSE;
|
||||
expectedEvents.push_back(event);
|
||||
|
||||
VERIFY_ARE_EQUAL(wch, keyEvent->GetCharData());
|
||||
VERIFY_ARE_EQUAL(isKeyDown, keyEvent->IsKeyDown());
|
||||
VERIFY_ARE_EQUAL(1, keyEvent->GetRepeatCount());
|
||||
VERIFY_ARE_EQUAL(static_cast<DWORD>(0), keyEvent->GetActiveModifierKeys());
|
||||
VERIFY_ARE_EQUAL(virtualScanCode, keyEvent->GetVirtualScanCode());
|
||||
VERIFY_ARE_EQUAL(LOBYTE(keyState), keyEvent->GetVirtualKeyCode());
|
||||
if (shift)
|
||||
{
|
||||
expectedEvents.push_back(shiftUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(CanConvertUppercaseText)
|
||||
{
|
||||
std::wstring wstr = L"HeLlO WoRlD";
|
||||
size_t uppercaseCount = 0;
|
||||
for (auto wch : wstr)
|
||||
VERIFY_ARE_EQUAL(expectedEvents.size(), events.size());
|
||||
|
||||
for (size_t i = 0; i < events.size(); ++i)
|
||||
{
|
||||
std::isupper(wch) ? ++uppercaseCount : 0;
|
||||
}
|
||||
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
|
||||
wstr.size());
|
||||
|
||||
VERIFY_ARE_EQUAL((wstr.size() + uppercaseCount) * 2, events.size());
|
||||
for (auto wch : wstr)
|
||||
{
|
||||
std::deque<bool> keydownPattern{ true, false };
|
||||
for (auto isKeyDown : keydownPattern)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"testing char: %C; keydown: %d", wch, isKeyDown));
|
||||
|
||||
VERIFY_ARE_EQUAL(InputEventType::KeyEvent, events.front()->EventType());
|
||||
std::unique_ptr<KeyEvent> keyEvent;
|
||||
keyEvent.reset(static_cast<KeyEvent* const>(events.front().release()));
|
||||
events.pop_front();
|
||||
|
||||
const short keyScanError = -1;
|
||||
const auto keyState = OneCoreSafeVkKeyScanW(wch);
|
||||
VERIFY_ARE_NOT_EQUAL(keyScanError, keyState);
|
||||
const auto virtualScanCode = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(LOBYTE(keyState), MAPVK_VK_TO_VSC));
|
||||
|
||||
if (std::isupper(wch))
|
||||
{
|
||||
// uppercase letters have shift key events
|
||||
// surrounding them, making two events per letter
|
||||
// (and another two for the keyup)
|
||||
VERIFY_IS_FALSE(events.empty());
|
||||
|
||||
VERIFY_ARE_EQUAL(InputEventType::KeyEvent, events.front()->EventType());
|
||||
std::unique_ptr<KeyEvent> keyEvent2;
|
||||
keyEvent2.reset(static_cast<KeyEvent* const>(events.front().release()));
|
||||
events.pop_front();
|
||||
|
||||
const auto keyState2 = OneCoreSafeVkKeyScanW(wch);
|
||||
VERIFY_ARE_NOT_EQUAL(keyScanError, keyState2);
|
||||
const auto virtualScanCode2 = static_cast<WORD>(OneCoreSafeMapVirtualKeyW(LOBYTE(keyState2), MAPVK_VK_TO_VSC));
|
||||
|
||||
if (isKeyDown)
|
||||
{
|
||||
// shift then letter
|
||||
const KeyEvent shiftDownEvent{ TRUE, 1, VK_SHIFT, leftShiftScanCode, L'\0', SHIFT_PRESSED };
|
||||
VERIFY_ARE_EQUAL(shiftDownEvent, *keyEvent);
|
||||
|
||||
const KeyEvent expectedKeyEvent{ TRUE, 1, LOBYTE(keyState2), virtualScanCode2, wch, SHIFT_PRESSED };
|
||||
VERIFY_ARE_EQUAL(expectedKeyEvent, *keyEvent2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// letter then shift
|
||||
const KeyEvent expectedKeyEvent{ FALSE, 1, LOBYTE(keyState), virtualScanCode, wch, SHIFT_PRESSED };
|
||||
VERIFY_ARE_EQUAL(expectedKeyEvent, *keyEvent);
|
||||
|
||||
const KeyEvent shiftUpEvent{ FALSE, 1, VK_SHIFT, leftShiftScanCode, L'\0', 0 };
|
||||
VERIFY_ARE_EQUAL(shiftUpEvent, *keyEvent2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const KeyEvent expectedKeyEvent{ !!isKeyDown, 1, LOBYTE(keyState), virtualScanCode, wch, 0 };
|
||||
VERIFY_ARE_EQUAL(expectedKeyEvent, *keyEvent);
|
||||
}
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedEvents[i], events[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,23 +213,22 @@ class ClipboardTests
|
||||
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
|
||||
wstr.size());
|
||||
|
||||
std::deque<KeyEvent> expectedEvents;
|
||||
InputEventQueue expectedEvents;
|
||||
// should be converted to:
|
||||
// 1. AltGr keydown
|
||||
// 2. € keydown
|
||||
// 3. € keyup
|
||||
// 4. AltGr keyup
|
||||
expectedEvents.push_back({ TRUE, 1, VK_MENU, altScanCode, L'\0', (ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) });
|
||||
expectedEvents.push_back({ TRUE, 1, virtualKeyCode, virtualScanCode, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) });
|
||||
expectedEvents.push_back({ FALSE, 1, virtualKeyCode, virtualScanCode, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) });
|
||||
expectedEvents.push_back({ FALSE, 1, VK_MENU, altScanCode, L'\0', ENHANCED_KEY });
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, L'\0', (ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)));
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, virtualKeyCode, virtualScanCode, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)));
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, virtualKeyCode, virtualScanCode, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)));
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, L'\0', ENHANCED_KEY));
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedEvents.size(), events.size());
|
||||
|
||||
for (size_t i = 0; i < events.size(); ++i)
|
||||
{
|
||||
const auto currentKeyEvent = *reinterpret_cast<const KeyEvent* const>(events[i].get());
|
||||
VERIFY_ARE_EQUAL(expectedEvents[i], currentKeyEvent, NoThrowString().Format(L"i == %d", i));
|
||||
VERIFY_ARE_EQUAL(expectedEvents[i], events[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,7 +240,7 @@ class ClipboardTests
|
||||
auto events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
|
||||
wstr.size());
|
||||
|
||||
std::deque<KeyEvent> expectedEvents;
|
||||
InputEventQueue expectedEvents;
|
||||
if constexpr (Feature_UseNumpadEventsForClipboardInput::IsEnabled())
|
||||
{
|
||||
// Inside Windows, where numpad events are enabled, this generated numpad events.
|
||||
@ -313,26 +251,25 @@ class ClipboardTests
|
||||
// 4. 2nd numpad keydown
|
||||
// 5. 2nd numpad keyup
|
||||
// 6. left alt keyup
|
||||
expectedEvents.push_back({ TRUE, 1, VK_MENU, altScanCode, L'\0', LEFT_ALT_PRESSED });
|
||||
expectedEvents.push_back({ TRUE, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED });
|
||||
expectedEvents.push_back({ FALSE, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED });
|
||||
expectedEvents.push_back({ TRUE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED });
|
||||
expectedEvents.push_back({ FALSE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED });
|
||||
expectedEvents.push_back({ FALSE, 1, VK_MENU, altScanCode, wstr[0], 0 });
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, L'\0', LEFT_ALT_PRESSED));
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED));
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED));
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED));
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED));
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, wstr[0], 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outside Windows, without numpad events, we just emit the key with a nonzero UnicodeChar
|
||||
expectedEvents.push_back({ TRUE, 1, 0, 0, wstr[0], 0 });
|
||||
expectedEvents.push_back({ FALSE, 1, 0, 0, wstr[0], 0 });
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wstr[0], 0));
|
||||
expectedEvents.push_back(SynthesizeKeyEvent(false, 1, 0, 0, wstr[0], 0));
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedEvents.size(), events.size());
|
||||
|
||||
for (size_t i = 0; i < events.size(); ++i)
|
||||
{
|
||||
const auto currentKeyEvent = *reinterpret_cast<const KeyEvent* const>(events[i].get());
|
||||
VERIFY_ARE_EQUAL(expectedEvents[i], currentKeyEvent, NoThrowString().Format(L"i == %d", i));
|
||||
VERIFY_ARE_EQUAL(expectedEvents[i], events[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -39,7 +39,6 @@
|
||||
<ClCompile Include="VtIoTests.cpp" />
|
||||
<ClCompile Include="VtRendererTests.cpp" />
|
||||
<ClCompile Include="ConptyOutputTests.cpp" />
|
||||
<Clcompile Include="..\..\types\IInputEventStreams.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
||||
@ -63,9 +63,6 @@
|
||||
<ClCompile Include="VtRendererTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<Clcompile Include="..\..\types\IInputEventStreams.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</Clcompile>
|
||||
<ClCompile Include="AliasTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@ -61,12 +61,12 @@ class InputBufferTests
|
||||
{
|
||||
InputBuffer inputBuffer;
|
||||
auto record = MakeKeyEvent(true, 1, L'a', 0, L'a', 0);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 1u);
|
||||
// add another event, check again
|
||||
INPUT_RECORD record2;
|
||||
record2.EventType = MENU_EVENT;
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record2)), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record2), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 2u);
|
||||
}
|
||||
|
||||
@ -77,8 +77,8 @@ class InputBufferTests
|
||||
{
|
||||
INPUT_RECORD record;
|
||||
record.EventType = MENU_EVENT;
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
|
||||
VERIFY_ARE_EQUAL(record, inputBuffer._storage.back()->ToInputRecord());
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
|
||||
VERIFY_ARE_EQUAL(record, inputBuffer._storage.back());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
|
||||
}
|
||||
@ -86,19 +86,19 @@ class InputBufferTests
|
||||
TEST_METHOD(CanBulkInsertIntoInputBuffer)
|
||||
{
|
||||
InputBuffer inputBuffer;
|
||||
std::deque<std::unique_ptr<IInputEvent>> events;
|
||||
InputEventQueue events;
|
||||
INPUT_RECORD record;
|
||||
record.EventType = MENU_EVENT;
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
events.push_back(IInputEvent::Create(record));
|
||||
events.push_back(record);
|
||||
}
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(events), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
|
||||
// verify that the events are the same in storage
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage[i]->ToInputRecord(), record);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage[i], record);
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,23 +115,22 @@ class InputBufferTests
|
||||
{
|
||||
mouseRecord.Event.MouseEvent.dwMousePosition.X = static_cast<SHORT>(i + 1);
|
||||
mouseRecord.Event.MouseEvent.dwMousePosition.Y = static_cast<SHORT>(i + 1) * 2;
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(mouseRecord)), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(mouseRecord), 0u);
|
||||
}
|
||||
|
||||
// check that they coalesced
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 1u);
|
||||
// check that the mouse position is being updated correctly
|
||||
const IInputEvent* const pOutEvent = inputBuffer._storage.front().get();
|
||||
const auto pMouseEvent = static_cast<const MouseEvent* const>(pOutEvent);
|
||||
VERIFY_ARE_EQUAL(pMouseEvent->GetPosition().x, static_cast<SHORT>(RECORD_INSERT_COUNT));
|
||||
VERIFY_ARE_EQUAL(pMouseEvent->GetPosition().y, static_cast<SHORT>(RECORD_INSERT_COUNT * 2));
|
||||
const auto& pMouseEvent = inputBuffer._storage.front().Event.MouseEvent;
|
||||
VERIFY_ARE_EQUAL(pMouseEvent.dwMousePosition.X, static_cast<SHORT>(RECORD_INSERT_COUNT));
|
||||
VERIFY_ARE_EQUAL(pMouseEvent.dwMousePosition.Y, static_cast<SHORT>(RECORD_INSERT_COUNT * 2));
|
||||
|
||||
// add a key event and another mouse event to make sure that
|
||||
// an event between two mouse events stopped the coalescing.
|
||||
INPUT_RECORD keyRecord;
|
||||
keyRecord.EventType = KEY_EVENT;
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(keyRecord)), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(mouseRecord)), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(keyRecord), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(mouseRecord), 0u);
|
||||
|
||||
// verify
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 3u);
|
||||
@ -143,29 +142,25 @@ class InputBufferTests
|
||||
|
||||
InputBuffer inputBuffer;
|
||||
INPUT_RECORD mouseRecords[RECORD_INSERT_COUNT];
|
||||
std::deque<std::unique_ptr<IInputEvent>> events;
|
||||
InputEventQueue events;
|
||||
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
mouseRecords[i].EventType = MOUSE_EVENT;
|
||||
mouseRecords[i].Event.MouseEvent.dwEventFlags = MOUSE_MOVED;
|
||||
events.push_back(IInputEvent::Create(mouseRecords[i]));
|
||||
events.push_back(mouseRecords[i]);
|
||||
}
|
||||
// add an extra event
|
||||
events.push_front(IInputEvent::Create(mouseRecords[0]));
|
||||
inputBuffer.Flush();
|
||||
// send one mouse event to possibly coalesce into later
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(std::move(events.front())), 0u);
|
||||
events.pop_front();
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(mouseRecords[0]), 0u);
|
||||
// write the others in bulk
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(events), 0u);
|
||||
// no events should have been coalesced
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT + 1);
|
||||
// check that the events stored match those inserted
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.front()->ToInputRecord(), mouseRecords[0]);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.front(), mouseRecords[0]);
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage[i + 1]->ToInputRecord(), mouseRecords[i]);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage[i + 1], mouseRecords[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,7 +175,7 @@ class InputBufferTests
|
||||
inputBuffer.Flush();
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
|
||||
}
|
||||
|
||||
// all events should have been coalesced into one
|
||||
@ -188,16 +183,12 @@ class InputBufferTests
|
||||
|
||||
// the single event should have a repeat count for each
|
||||
// coalesced event
|
||||
std::unique_ptr<IInputEvent> outEvent;
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvent,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false));
|
||||
InputEventQueue outEvents;
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents, 1, true, false, false, false));
|
||||
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, outEvent.get());
|
||||
const auto pKeyEvent = static_cast<const KeyEvent* const>(outEvent.get());
|
||||
VERIFY_ARE_EQUAL(pKeyEvent->GetRepeatCount(), RECORD_INSERT_COUNT);
|
||||
VERIFY_IS_FALSE(outEvents.empty());
|
||||
const auto& pKeyEvent = outEvents.front().Event.KeyEvent;
|
||||
VERIFY_ARE_EQUAL(pKeyEvent.wRepeatCount, RECORD_INSERT_COUNT);
|
||||
}
|
||||
|
||||
TEST_METHOD(InputBufferDoesNotCoalesceBulkKeyEvents)
|
||||
@ -206,25 +197,25 @@ class InputBufferTests
|
||||
|
||||
InputBuffer inputBuffer;
|
||||
INPUT_RECORD keyRecords[RECORD_INSERT_COUNT];
|
||||
std::deque<std::unique_ptr<IInputEvent>> events;
|
||||
InputEventQueue events;
|
||||
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
keyRecords[i] = MakeKeyEvent(true, 1, L'a', 0, L'a', 0);
|
||||
events.push_back(IInputEvent::Create(keyRecords[i]));
|
||||
events.push_back(keyRecords[i]);
|
||||
}
|
||||
inputBuffer.Flush();
|
||||
// send one key event to possibly coalesce into later
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(keyRecords[0])), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(keyRecords[0]), 0u);
|
||||
// write the others in bulk
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(events), 0u);
|
||||
// no events should have been coalesced
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT + 1);
|
||||
// check that the events stored match those inserted
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.front()->ToInputRecord(), keyRecords[0]);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.front(), keyRecords[0]);
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage[i + 1]->ToInputRecord(), keyRecords[i]);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage[i + 1], keyRecords[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,8 +229,8 @@ class InputBufferTests
|
||||
inputBuffer.Flush();
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.back()->ToInputRecord(), record);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.back(), record);
|
||||
}
|
||||
|
||||
// The events shouldn't be coalesced
|
||||
@ -249,14 +240,14 @@ class InputBufferTests
|
||||
TEST_METHOD(CanFlushAllOutput)
|
||||
{
|
||||
InputBuffer inputBuffer;
|
||||
std::deque<std::unique_ptr<IInputEvent>> events;
|
||||
InputEventQueue events;
|
||||
|
||||
// put some events in the buffer so we can remove them
|
||||
INPUT_RECORD record;
|
||||
record.EventType = MENU_EVENT;
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
events.push_back(IInputEvent::Create(record));
|
||||
events.push_back(record);
|
||||
}
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(events), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
|
||||
@ -270,13 +261,13 @@ class InputBufferTests
|
||||
{
|
||||
InputBuffer inputBuffer;
|
||||
INPUT_RECORD records[RECORD_INSERT_COUNT] = { 0 };
|
||||
std::deque<std::unique_ptr<IInputEvent>> inEvents;
|
||||
InputEventQueue inEvents;
|
||||
|
||||
// create alternating mouse and key events
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
records[i].EventType = (i % 2 == 0) ? MENU_EVENT : KEY_EVENT;
|
||||
inEvents.push_back(IInputEvent::Create(records[i]));
|
||||
inEvents.push_back(records[i]);
|
||||
}
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
|
||||
@ -286,7 +277,7 @@ class InputBufferTests
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT / 2);
|
||||
|
||||
// make sure that the non key events were the ones removed
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
auto amountToRead = RECORD_INSERT_COUNT / 2;
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
|
||||
amountToRead,
|
||||
@ -298,7 +289,7 @@ class InputBufferTests
|
||||
|
||||
for (size_t i = 0; i < outEvents.size(); ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(outEvents[i]->EventType(), InputEventType::KeyEvent);
|
||||
VERIFY_ARE_EQUAL(outEvents[i].EventType, KEY_EVENT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,18 +297,18 @@ class InputBufferTests
|
||||
{
|
||||
InputBuffer inputBuffer;
|
||||
INPUT_RECORD records[RECORD_INSERT_COUNT];
|
||||
std::deque<std::unique_ptr<IInputEvent>> inEvents;
|
||||
InputEventQueue inEvents;
|
||||
|
||||
// write some input records
|
||||
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
records[i] = MakeKeyEvent(TRUE, 1, static_cast<WCHAR>(L'A' + i), 0, static_cast<WCHAR>(L'A' + i), 0);
|
||||
inEvents.push_back(IInputEvent::Create(records[i]));
|
||||
inEvents.push_back(records[i]);
|
||||
}
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
|
||||
|
||||
// read them back out
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
auto amountToRead = RECORD_INSERT_COUNT;
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
|
||||
amountToRead,
|
||||
@ -329,7 +320,7 @@ class InputBufferTests
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
|
||||
for (size_t i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(records[i], outEvents[i]->ToInputRecord());
|
||||
VERIFY_ARE_EQUAL(records[i], outEvents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,16 +330,16 @@ class InputBufferTests
|
||||
|
||||
// add some events so that we have something to peek at
|
||||
INPUT_RECORD records[RECORD_INSERT_COUNT];
|
||||
std::deque<std::unique_ptr<IInputEvent>> inEvents;
|
||||
InputEventQueue inEvents;
|
||||
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
records[i] = MakeKeyEvent(TRUE, 1, static_cast<WCHAR>(L'A' + i), 0, static_cast<WCHAR>(L'A' + i), 0);
|
||||
inEvents.push_back(IInputEvent::Create(records[i]));
|
||||
inEvents.push_back(records[i]);
|
||||
}
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
|
||||
|
||||
// peek at events
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
auto amountToRead = RECORD_INSERT_COUNT;
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
|
||||
amountToRead,
|
||||
@ -361,7 +352,7 @@ class InputBufferTests
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
|
||||
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(records[i], outEvents[i]->ToInputRecord());
|
||||
VERIFY_ARE_EQUAL(records[i], outEvents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,11 +364,11 @@ class InputBufferTests
|
||||
|
||||
// add some events so that we have something to stick in front of
|
||||
INPUT_RECORD records[RECORD_INSERT_COUNT];
|
||||
std::deque<std::unique_ptr<IInputEvent>> inEvents;
|
||||
InputEventQueue inEvents;
|
||||
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
records[i] = MakeKeyEvent(TRUE, 1, static_cast<WCHAR>(L'A' + i), 0, static_cast<WCHAR>(L'A' + i), 0);
|
||||
inEvents.push_back(IInputEvent::Create(records[i]));
|
||||
inEvents.push_back(records[i]);
|
||||
}
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
|
||||
|
||||
@ -385,7 +376,7 @@ class InputBufferTests
|
||||
waitEvent.SetEvent();
|
||||
|
||||
// read one record, hInputEvent should still be signaled
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
|
||||
1,
|
||||
false,
|
||||
@ -435,17 +426,11 @@ class InputBufferTests
|
||||
outRecordsExpected[3] = MakeKeyEvent(TRUE, 1, 0x3042, 0, 0xa0, 0);
|
||||
outRecordsExpected[4].EventType = MOUSE_EVENT;
|
||||
|
||||
std::deque<std::unique_ptr<IInputEvent>> inEvents;
|
||||
for (size_t i = 0; i < inRecords.size(); ++i)
|
||||
{
|
||||
inEvents.push_back(IInputEvent::Create(inRecords[i]));
|
||||
}
|
||||
|
||||
inputBuffer.Flush();
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inRecords), 0u);
|
||||
|
||||
// read them out non-unicode style and compare
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
|
||||
outRecordsExpected.size(),
|
||||
false,
|
||||
@ -455,7 +440,7 @@ class InputBufferTests
|
||||
VERIFY_ARE_EQUAL(outEvents.size(), outRecordsExpected.size());
|
||||
for (size_t i = 0; i < outEvents.size(); ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(outEvents[i]->ToInputRecord(), outRecordsExpected[i]);
|
||||
VERIFY_ARE_EQUAL(outEvents[i], outRecordsExpected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -465,11 +450,11 @@ class InputBufferTests
|
||||
|
||||
// add some events so that we have something to stick in front of
|
||||
INPUT_RECORD records[RECORD_INSERT_COUNT];
|
||||
std::deque<std::unique_ptr<IInputEvent>> inEvents;
|
||||
InputEventQueue inEvents;
|
||||
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
records[i] = MakeKeyEvent(TRUE, 1, static_cast<WCHAR>(L'A' + i), 0, static_cast<WCHAR>(L'A' + i), 0);
|
||||
inEvents.push_back(IInputEvent::Create(records[i]));
|
||||
inEvents.push_back(records[i]);
|
||||
}
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(inEvents), 0u);
|
||||
|
||||
@ -479,13 +464,13 @@ class InputBufferTests
|
||||
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
prependRecords[i] = MakeKeyEvent(TRUE, 1, static_cast<WCHAR>(L'a' + i), 0, static_cast<WCHAR>(L'a' + i), 0);
|
||||
inEvents.push_back(IInputEvent::Create(prependRecords[i]));
|
||||
inEvents.push_back(prependRecords[i]);
|
||||
}
|
||||
auto eventsWritten = inputBuffer.Prepend(inEvents);
|
||||
VERIFY_ARE_EQUAL(eventsWritten, RECORD_INSERT_COUNT);
|
||||
|
||||
// grab the first set of events and ensure they match prependRecords
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
auto amountToRead = RECORD_INSERT_COUNT;
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
|
||||
amountToRead,
|
||||
@ -497,7 +482,7 @@ class InputBufferTests
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), RECORD_INSERT_COUNT);
|
||||
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(prependRecords[i], outEvents[i]->ToInputRecord());
|
||||
VERIFY_ARE_EQUAL(prependRecords[i], outEvents[i]);
|
||||
}
|
||||
|
||||
outEvents.clear();
|
||||
@ -512,7 +497,7 @@ class InputBufferTests
|
||||
VERIFY_ARE_EQUAL(amountToRead, outEvents.size());
|
||||
for (unsigned int i = 0; i < RECORD_INSERT_COUNT; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(records[i], outEvents[i]->ToInputRecord());
|
||||
VERIFY_ARE_EQUAL(records[i], outEvents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -524,7 +509,7 @@ class InputBufferTests
|
||||
// change the buffer's state a bit
|
||||
INPUT_RECORD record;
|
||||
record.EventType = MENU_EVENT;
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(record)), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(record), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 1u);
|
||||
inputBuffer.InputMode = 0x0;
|
||||
inputBuffer.ReinitializeInputBuffer();
|
||||
@ -544,7 +529,7 @@ class InputBufferTests
|
||||
VERIFY_IS_FALSE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
|
||||
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(pauseRecord)), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(pauseRecord), 0u);
|
||||
|
||||
// we should now be paused and the input record should be discarded
|
||||
VERIFY_IS_TRUE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
|
||||
@ -552,7 +537,7 @@ class InputBufferTests
|
||||
|
||||
// the next key press should unpause us but be discarded
|
||||
auto unpauseRecord = MakeKeyEvent(true, 1, L'a', 0, L'a', 0);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(unpauseRecord)), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(unpauseRecord), 0u);
|
||||
|
||||
VERIFY_IS_FALSE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
|
||||
@ -569,7 +554,7 @@ class InputBufferTests
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 0u);
|
||||
|
||||
// pause the screen
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(pauseRecord)), 0u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(pauseRecord), 0u);
|
||||
|
||||
// we should now be paused and the input record should be discarded
|
||||
VERIFY_IS_TRUE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
|
||||
@ -578,12 +563,12 @@ class InputBufferTests
|
||||
// sending a system key event should not stop the pause and
|
||||
// the record should be stored in the input buffer
|
||||
auto systemRecord = MakeKeyEvent(true, 1, VK_CONTROL, 0, 0, 0);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(IInputEvent::Create(systemRecord)), 0u);
|
||||
VERIFY_IS_GREATER_THAN(inputBuffer.Write(systemRecord), 0u);
|
||||
|
||||
VERIFY_IS_TRUE(WI_IsFlagSet(gci.Flags, CONSOLE_OUTPUT_SUSPENDED));
|
||||
VERIFY_ARE_EQUAL(inputBuffer.GetNumberOfReadyEvents(), 1u);
|
||||
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
size_t amountToRead = 2;
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
|
||||
amountToRead,
|
||||
@ -597,18 +582,18 @@ class InputBufferTests
|
||||
{
|
||||
InputBuffer inputBuffer;
|
||||
auto record = MakeKeyEvent(true, 1, L'a', 0, L'a', 0);
|
||||
auto inputEvent = IInputEvent::Create(record);
|
||||
auto inputEvent = record;
|
||||
size_t eventsWritten;
|
||||
auto waitEvent = false;
|
||||
inputBuffer.Flush();
|
||||
// write one event to an empty buffer
|
||||
std::deque<std::unique_ptr<IInputEvent>> storage;
|
||||
InputEventQueue storage;
|
||||
storage.push_back(std::move(inputEvent));
|
||||
inputBuffer._WriteBuffer(storage, eventsWritten, waitEvent);
|
||||
VERIFY_IS_TRUE(waitEvent);
|
||||
// write another, it shouldn't signal this time
|
||||
auto record2 = MakeKeyEvent(true, 1, L'b', 0, L'b', 0);
|
||||
auto inputEvent2 = IInputEvent::Create(record2);
|
||||
auto inputEvent2 = record2;
|
||||
// write another event to a non-empty buffer
|
||||
waitEvent = false;
|
||||
storage.clear();
|
||||
@ -623,9 +608,9 @@ class InputBufferTests
|
||||
InputBuffer inputBuffer;
|
||||
const WORD repeatCount = 5;
|
||||
auto record = MakeKeyEvent(true, repeatCount, L'a', 0, L'a', 0);
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(record)), 1u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(record), 1u);
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
|
||||
1,
|
||||
false,
|
||||
@ -634,8 +619,8 @@ class InputBufferTests
|
||||
true));
|
||||
VERIFY_ARE_EQUAL(outEvents.size(), 1u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.size(), 1u);
|
||||
VERIFY_ARE_EQUAL(static_cast<const KeyEvent&>(*inputBuffer._storage.front()).GetRepeatCount(), repeatCount - 1);
|
||||
VERIFY_ARE_EQUAL(static_cast<const KeyEvent&>(*outEvents.front()).GetRepeatCount(), 1u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.front().Event.KeyEvent.wRepeatCount, repeatCount - 1);
|
||||
VERIFY_ARE_EQUAL(outEvents.front().Event.KeyEvent.wRepeatCount, 1u);
|
||||
}
|
||||
|
||||
TEST_METHOD(StreamPeekingDeCoalesces)
|
||||
@ -643,9 +628,9 @@ class InputBufferTests
|
||||
InputBuffer inputBuffer;
|
||||
const WORD repeatCount = 5;
|
||||
auto record = MakeKeyEvent(true, repeatCount, L'a', 0, L'a', 0);
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(IInputEvent::Create(record)), 1u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer.Write(record), 1u);
|
||||
VERIFY_NT_SUCCESS(inputBuffer.Read(outEvents,
|
||||
1,
|
||||
true,
|
||||
@ -654,7 +639,7 @@ class InputBufferTests
|
||||
true));
|
||||
VERIFY_ARE_EQUAL(outEvents.size(), 1u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.size(), 1u);
|
||||
VERIFY_ARE_EQUAL(static_cast<const KeyEvent&>(*inputBuffer._storage.front()).GetRepeatCount(), repeatCount);
|
||||
VERIFY_ARE_EQUAL(static_cast<const KeyEvent&>(*outEvents.front()).GetRepeatCount(), 1u);
|
||||
VERIFY_ARE_EQUAL(inputBuffer._storage.front().Event.KeyEvent.wRepeatCount, repeatCount);
|
||||
VERIFY_ARE_EQUAL(outEvents.front().Event.KeyEvent.wRepeatCount, 1u);
|
||||
}
|
||||
};
|
||||
|
||||
@ -278,6 +278,12 @@ namespace til
|
||||
return tmp;
|
||||
}
|
||||
|
||||
[[nodiscard]] friend constexpr small_vector_iterator operator+(const difference_type off, small_vector_iterator next) noexcept
|
||||
{
|
||||
next += off;
|
||||
return next;
|
||||
}
|
||||
|
||||
constexpr small_vector_iterator& operator-=(const difference_type off) noexcept
|
||||
{
|
||||
base::operator-=(off);
|
||||
|
||||
@ -3,10 +3,6 @@
|
||||
|
||||
#include "precomp.h"
|
||||
#include "../inc/EventSynthesis.hpp"
|
||||
#include "../../types/inc/convert.hpp"
|
||||
#include "../inc/VtApiRedirection.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
// TODO: MSFT 14150722 - can these const values be generated at
|
||||
// runtime without breaking compatibility?
|
||||
@ -16,35 +12,29 @@ static constexpr WORD leftShiftScanCode = 0x2A;
|
||||
// Routine Description:
|
||||
// - naively determines the width of a UCS2 encoded wchar (with caveats noted above)
|
||||
#pragma warning(suppress : 4505) // this function will be deleted if numpad events are disabled
|
||||
static CodepointWidth GetQuickCharWidthLegacyForNumpadEventSynthesis(const wchar_t wch) noexcept
|
||||
static bool IsCharFullWidth(const wchar_t wch) noexcept
|
||||
{
|
||||
if ((0x1100 <= wch && wch <= 0x115f) // From Unicode 9.0, Hangul Choseong is wide
|
||||
|| (0x2e80 <= wch && wch <= 0x303e) // From Unicode 9.0, this range is wide (assorted languages)
|
||||
|| (0x3041 <= wch && wch <= 0x3094) // Hiragana
|
||||
|| (0x30a1 <= wch && wch <= 0x30f6) // Katakana
|
||||
|| (0x3105 <= wch && wch <= 0x312c) // Bopomofo
|
||||
|| (0x3131 <= wch && wch <= 0x318e) // Hangul Elements
|
||||
|| (0x3190 <= wch && wch <= 0x3247) // From Unicode 9.0, this range is wide
|
||||
|| (0x3251 <= wch && wch <= 0x4dbf) // Unicode 9.0 CJK Unified Ideographs, Yi, Reserved, Han Ideograph (hexagrams from 4DC0..4DFF are ignored
|
||||
|| (0x4e00 <= wch && wch <= 0xa4c6) // Unicode 9.0 CJK Unified Ideographs, Yi, Reserved, Han Ideograph (hexagrams from 4DC0..4DFF are ignored
|
||||
|| (0xa960 <= wch && wch <= 0xa97c) // Wide Hangul Choseong
|
||||
|| (0xac00 <= wch && wch <= 0xd7a3) // Korean Hangul Syllables
|
||||
|| (0xf900 <= wch && wch <= 0xfaff) // From Unicode 9.0, this range is wide [CJK Compatibility Ideographs, Includes Han Compatibility Ideographs]
|
||||
|| (0xfe10 <= wch && wch <= 0xfe1f) // From Unicode 9.0, this range is wide [Presentation forms]
|
||||
|| (0xfe30 <= wch && wch <= 0xfe6b) // From Unicode 9.0, this range is wide [Presentation forms]
|
||||
|| (0xff01 <= wch && wch <= 0xff5e) // Fullwidth ASCII variants
|
||||
|| (0xffe0 <= wch && wch <= 0xffe6)) // Fullwidth symbol variants
|
||||
{
|
||||
return CodepointWidth::Wide;
|
||||
}
|
||||
|
||||
return CodepointWidth::Narrow;
|
||||
return (0x1100 <= wch && wch <= 0x115f) || // From Unicode 9.0, Hangul Choseong is wide
|
||||
(0x2e80 <= wch && wch <= 0x303e) || // From Unicode 9.0, this range is wide (assorted languages)
|
||||
(0x3041 <= wch && wch <= 0x3094) || // Hiragana
|
||||
(0x30a1 <= wch && wch <= 0x30f6) || // Katakana
|
||||
(0x3105 <= wch && wch <= 0x312c) || // Bopomofo
|
||||
(0x3131 <= wch && wch <= 0x318e) || // Hangul Elements
|
||||
(0x3190 <= wch && wch <= 0x3247) || // From Unicode 9.0, this range is wide
|
||||
(0x3251 <= wch && wch <= 0x4dbf) || // Unicode 9.0 CJK Unified Ideographs, Yi, Reserved, Han Ideograph (hexagrams from 4DC0..4DFF are ignored
|
||||
(0x4e00 <= wch && wch <= 0xa4c6) || // Unicode 9.0 CJK Unified Ideographs, Yi, Reserved, Han Ideograph (hexagrams from 4DC0..4DFF are ignored
|
||||
(0xa960 <= wch && wch <= 0xa97c) || // Wide Hangul Choseong
|
||||
(0xac00 <= wch && wch <= 0xd7a3) || // Korean Hangul Syllables
|
||||
(0xf900 <= wch && wch <= 0xfaff) || // From Unicode 9.0, this range is wide [CJK Compatibility Ideographs, Includes Han Compatibility Ideographs]
|
||||
(0xfe10 <= wch && wch <= 0xfe1f) || // From Unicode 9.0, this range is wide [Presentation forms]
|
||||
(0xfe30 <= wch && wch <= 0xfe6b) || // From Unicode 9.0, this range is wide [Presentation forms]
|
||||
(0xff01 <= wch && wch <= 0xff5e) || // Fullwidth ASCII variants
|
||||
(0xffe0 <= wch && wch <= 0xffe6); // Fullwidth symbol variants
|
||||
}
|
||||
|
||||
std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::CharToKeyEvents(const wchar_t wch,
|
||||
const unsigned int codepage)
|
||||
void Microsoft::Console::Interactivity::CharToKeyEvents(const wchar_t wch, const unsigned int codepage, InputEventQueue& keyEvents)
|
||||
{
|
||||
const short invalidKey = -1;
|
||||
static constexpr short invalidKey = -1;
|
||||
auto keyState = OneCoreSafeVkKeyScanW(wch);
|
||||
|
||||
if (keyState == invalidKey)
|
||||
@ -57,18 +47,19 @@ std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::CharToK
|
||||
WORD CharType = 0;
|
||||
GetStringTypeW(CT_CTYPE3, &wch, 1, &CharType);
|
||||
|
||||
if (!(WI_IsFlagSet(CharType, C3_ALPHA) || GetQuickCharWidthLegacyForNumpadEventSynthesis(wch) == CodepointWidth::Wide))
|
||||
if (WI_IsFlagClear(CharType, C3_ALPHA) && !IsCharFullWidth(wch))
|
||||
{
|
||||
// It wasn't alphanumeric or determined to be wide by the old algorithm
|
||||
// if VkKeyScanW fails (char is not in kbd layout), we must
|
||||
// emulate the key being input through the numpad
|
||||
return SynthesizeNumpadEvents(wch, codepage);
|
||||
SynthesizeNumpadEvents(wch, codepage, keyEvents);
|
||||
return;
|
||||
}
|
||||
}
|
||||
keyState = 0; // SynthesizeKeyboardEvents would rather get 0 than -1
|
||||
}
|
||||
|
||||
return SynthesizeKeyboardEvents(wch, keyState);
|
||||
SynthesizeKeyboardEvents(wch, keyState, keyEvents);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@ -80,80 +71,45 @@ std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::CharToK
|
||||
// - deque of KeyEvents that represent the wchar_t being typed
|
||||
// Note:
|
||||
// - will throw exception on error
|
||||
std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::SynthesizeKeyboardEvents(const wchar_t wch, const short keyState)
|
||||
void Microsoft::Console::Interactivity::SynthesizeKeyboardEvents(const wchar_t wch, const short keyState, InputEventQueue& keyEvents)
|
||||
{
|
||||
const auto modifierState = HIBYTE(keyState);
|
||||
|
||||
auto altGrSet = false;
|
||||
auto shiftSet = false;
|
||||
std::deque<std::unique_ptr<KeyEvent>> keyEvents;
|
||||
|
||||
// add modifier key event if necessary
|
||||
if (WI_AreAllFlagsSet(modifierState, VkKeyScanModState::CtrlAndAltPressed))
|
||||
{
|
||||
altGrSet = true;
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(true,
|
||||
1ui16,
|
||||
static_cast<WORD>(VK_MENU),
|
||||
altScanCode,
|
||||
UNICODE_NULL,
|
||||
(ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)));
|
||||
}
|
||||
else if (WI_IsFlagSet(modifierState, VkKeyScanModState::ShiftPressed))
|
||||
{
|
||||
shiftSet = true;
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(true,
|
||||
1ui16,
|
||||
static_cast<WORD>(VK_SHIFT),
|
||||
leftShiftScanCode,
|
||||
UNICODE_NULL,
|
||||
SHIFT_PRESSED));
|
||||
}
|
||||
|
||||
const auto vk = LOBYTE(keyState);
|
||||
const auto virtualScanCode = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
|
||||
KeyEvent keyEvent{ true, 1, LOBYTE(keyState), virtualScanCode, wch, 0 };
|
||||
const auto sc = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
|
||||
// The caller provides us with the result of VkKeyScanW() in keyState.
|
||||
// The magic constants below are the expected (documented) return values from VkKeyScanW().
|
||||
const auto modifierState = HIBYTE(keyState);
|
||||
const auto shiftSet = WI_IsFlagSet(modifierState, 1);
|
||||
const auto ctrlSet = WI_IsFlagSet(modifierState, 2);
|
||||
const auto altSet = WI_IsFlagSet(modifierState, 4);
|
||||
const auto altGrSet = WI_AreAllFlagsSet(modifierState, 4 | 2);
|
||||
|
||||
// add modifier flags if necessary
|
||||
if (WI_IsFlagSet(modifierState, VkKeyScanModState::ShiftPressed))
|
||||
{
|
||||
keyEvent.ActivateModifierKey(ModifierKeyState::Shift);
|
||||
}
|
||||
if (WI_IsFlagSet(modifierState, VkKeyScanModState::CtrlPressed))
|
||||
{
|
||||
keyEvent.ActivateModifierKey(ModifierKeyState::LeftCtrl);
|
||||
}
|
||||
if (WI_AreAllFlagsSet(modifierState, VkKeyScanModState::CtrlAndAltPressed))
|
||||
{
|
||||
keyEvent.ActivateModifierKey(ModifierKeyState::RightAlt);
|
||||
}
|
||||
|
||||
// add key event down and up
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
|
||||
keyEvent.SetKeyDown(false);
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(keyEvent));
|
||||
|
||||
// add modifier key up event
|
||||
if (altGrSet)
|
||||
{
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(false,
|
||||
1ui16,
|
||||
static_cast<WORD>(VK_MENU),
|
||||
altScanCode,
|
||||
UNICODE_NULL,
|
||||
ENHANCED_KEY));
|
||||
keyEvents.push_back(SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, 0, ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED));
|
||||
}
|
||||
else if (shiftSet)
|
||||
{
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(false,
|
||||
1ui16,
|
||||
static_cast<WORD>(VK_SHIFT),
|
||||
leftShiftScanCode,
|
||||
UNICODE_NULL,
|
||||
0));
|
||||
keyEvents.push_back(SynthesizeKeyEvent(true, 1, VK_SHIFT, leftShiftScanCode, 0, SHIFT_PRESSED));
|
||||
}
|
||||
|
||||
return keyEvents;
|
||||
auto keyEvent = SynthesizeKeyEvent(true, 1, vk, sc, wch, 0);
|
||||
WI_SetFlagIf(keyEvent.Event.KeyEvent.dwControlKeyState, SHIFT_PRESSED, shiftSet);
|
||||
WI_SetFlagIf(keyEvent.Event.KeyEvent.dwControlKeyState, LEFT_CTRL_PRESSED, ctrlSet);
|
||||
WI_SetFlagIf(keyEvent.Event.KeyEvent.dwControlKeyState, RIGHT_ALT_PRESSED, altSet);
|
||||
|
||||
keyEvents.push_back(keyEvent);
|
||||
keyEvent.Event.KeyEvent.bKeyDown = FALSE;
|
||||
keyEvents.push_back(keyEvent);
|
||||
|
||||
// handle yucky alt-gr keys
|
||||
if (altGrSet)
|
||||
{
|
||||
keyEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, 0, ENHANCED_KEY));
|
||||
}
|
||||
else if (shiftSet)
|
||||
{
|
||||
keyEvents.push_back(SynthesizeKeyEvent(false, 1, VK_SHIFT, leftShiftScanCode, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@ -166,64 +122,35 @@ std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::Synthes
|
||||
// alt + numpad
|
||||
// Note:
|
||||
// - will throw exception on error
|
||||
std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage)
|
||||
void Microsoft::Console::Interactivity::SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage, InputEventQueue& keyEvents)
|
||||
{
|
||||
std::deque<std::unique_ptr<KeyEvent>> keyEvents;
|
||||
char converted = 0;
|
||||
const auto result = WideCharToMultiByte(codepage, 0, &wch, 1, &converted, 1, nullptr, nullptr);
|
||||
|
||||
//alt keydown
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(true,
|
||||
1ui16,
|
||||
static_cast<WORD>(VK_MENU),
|
||||
altScanCode,
|
||||
UNICODE_NULL,
|
||||
LEFT_ALT_PRESSED));
|
||||
|
||||
std::wstring wstr{ wch };
|
||||
const auto convertedChars = ConvertToA(codepage, wstr);
|
||||
if (convertedChars.size() == 1)
|
||||
if (result == 1)
|
||||
{
|
||||
// alt keydown
|
||||
keyEvents.push_back(SynthesizeKeyEvent(true, 1, VK_MENU, altScanCode, 0, LEFT_ALT_PRESSED));
|
||||
|
||||
// It is OK if the char is "signed -1", we want to interpret that as "unsigned 255" for the
|
||||
// "integer to character" conversion below with ::to_string, thus the static_cast.
|
||||
// Prime example is nonbreaking space U+00A0 will convert to OEM by codepage 437 to 0xFF which is -1 signed.
|
||||
// But it is absolutely valid as 0xFF or 255 unsigned as the correct CP437 character.
|
||||
// We need to treat it as unsigned because we're going to pretend it was a keypad entry
|
||||
// and you don't enter negative numbers on the keypad.
|
||||
const auto uch = static_cast<unsigned char>(convertedChars.at(0));
|
||||
const auto charString = std::to_string(static_cast<unsigned char>(converted));
|
||||
|
||||
// unsigned char values are in the range [0, 255] so we need to be
|
||||
// able to store up to 4 chars from the conversion (including the end of string char)
|
||||
auto charString = std::to_string(uch);
|
||||
|
||||
for (auto& ch : std::string_view(charString))
|
||||
for (const auto& ch : charString)
|
||||
{
|
||||
if (ch == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const WORD virtualKey = ch - '0' + VK_NUMPAD0;
|
||||
const auto virtualScanCode = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(virtualKey, MAPVK_VK_TO_VSC));
|
||||
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(true,
|
||||
1ui16,
|
||||
virtualKey,
|
||||
virtualScanCode,
|
||||
UNICODE_NULL,
|
||||
LEFT_ALT_PRESSED));
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(false,
|
||||
1ui16,
|
||||
virtualKey,
|
||||
virtualScanCode,
|
||||
UNICODE_NULL,
|
||||
LEFT_ALT_PRESSED));
|
||||
const WORD vk = ch - '0' + VK_NUMPAD0;
|
||||
const auto sc = gsl::narrow<WORD>(OneCoreSafeMapVirtualKeyW(vk, MAPVK_VK_TO_VSC));
|
||||
auto keyEvent = SynthesizeKeyEvent(true, 1, vk, sc, 0, LEFT_ALT_PRESSED);
|
||||
keyEvents.push_back(keyEvent);
|
||||
keyEvent.Event.KeyEvent.bKeyDown = FALSE;
|
||||
keyEvents.push_back(keyEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// alt keyup
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(false,
|
||||
1ui16,
|
||||
static_cast<WORD>(VK_MENU),
|
||||
altScanCode,
|
||||
wch,
|
||||
0));
|
||||
return keyEvents;
|
||||
// alt keyup
|
||||
keyEvents.push_back(SynthesizeKeyEvent(false, 1, VK_MENU, altScanCode, wch, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,16 +14,10 @@ Author:
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include "../../types/inc/IInputEvent.hpp"
|
||||
|
||||
namespace Microsoft::Console::Interactivity
|
||||
{
|
||||
std::deque<std::unique_ptr<KeyEvent>> CharToKeyEvents(const wchar_t wch, const unsigned int codepage);
|
||||
|
||||
std::deque<std::unique_ptr<KeyEvent>> SynthesizeKeyboardEvents(const wchar_t wch,
|
||||
const short keyState);
|
||||
|
||||
std::deque<std::unique_ptr<KeyEvent>> SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage);
|
||||
void CharToKeyEvents(wchar_t wch, unsigned int codepage, InputEventQueue& out);
|
||||
void SynthesizeKeyboardEvents(wchar_t wch, short keyState, InputEventQueue& out);
|
||||
void SynthesizeNumpadEvents(wchar_t wch, unsigned int codepage, InputEventQueue& out);
|
||||
}
|
||||
|
||||
@ -266,9 +266,7 @@ VOID ConIoSrvComm::ServiceInputPipe()
|
||||
case CIS_EVENT_TYPE_INPUT:
|
||||
try
|
||||
{
|
||||
const auto keyRecord = Event.InputEvent.Record.Event.KeyEvent;
|
||||
const KeyEvent keyEvent{ keyRecord };
|
||||
HandleGenericKeyEvent(keyEvent, false);
|
||||
HandleGenericKeyEvent(Event.InputEvent.Record, false);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
||||
@ -134,17 +134,17 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData,
|
||||
// - deque of KeyEvents that represent the string passed in
|
||||
// Note:
|
||||
// - will throw exception on error
|
||||
std::deque<std::unique_ptr<IInputEvent>> Clipboard::TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
|
||||
const size_t cchData,
|
||||
const bool bracketedPaste)
|
||||
InputEventQueue Clipboard::TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
|
||||
const size_t cchData,
|
||||
const bool bracketedPaste)
|
||||
{
|
||||
THROW_HR_IF_NULL(E_INVALIDARG, pData);
|
||||
|
||||
std::deque<std::unique_ptr<IInputEvent>> keyEvents;
|
||||
InputEventQueue keyEvents;
|
||||
const auto pushControlSequence = [&](const std::wstring_view sequence) {
|
||||
std::for_each(sequence.begin(), sequence.end(), [&](const auto wch) {
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
|
||||
keyEvents.push_back(std::make_unique<KeyEvent>(false, 1ui16, 0ui16, 0ui16, wch, 0));
|
||||
keyEvents.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
|
||||
keyEvents.push_back(SynthesizeKeyEvent(false, 1, 0, 0, wch, 0));
|
||||
});
|
||||
};
|
||||
|
||||
@ -194,18 +194,14 @@ std::deque<std::unique_ptr<IInputEvent>> Clipboard::TextToKeyEvents(_In_reads_(c
|
||||
}
|
||||
|
||||
const auto codepage = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
|
||||
auto convertedEvents = CharToKeyEvents(currentChar, codepage);
|
||||
while (!convertedEvents.empty())
|
||||
{
|
||||
keyEvents.push_back(std::move(convertedEvents.front()));
|
||||
convertedEvents.pop_front();
|
||||
}
|
||||
CharToKeyEvents(currentChar, codepage, keyEvents);
|
||||
}
|
||||
|
||||
if (bracketedPaste)
|
||||
{
|
||||
pushControlSequence(L"\x1b[201~");
|
||||
}
|
||||
|
||||
return keyEvents;
|
||||
}
|
||||
|
||||
|
||||
@ -35,9 +35,9 @@ namespace Microsoft::Console::Interactivity::Win32
|
||||
void Paste();
|
||||
|
||||
private:
|
||||
std::deque<std::unique_ptr<IInputEvent>> TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
|
||||
const size_t cchData,
|
||||
const bool bracketedPaste = false);
|
||||
InputEventQueue TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
|
||||
const size_t cchData,
|
||||
const bool bracketedPaste = false);
|
||||
|
||||
void StoreSelectionToClipboard(_In_ const bool fAlsoCopyFormatting);
|
||||
|
||||
|
||||
@ -186,23 +186,19 @@ void HandleKeyEvent(const HWND hWnd,
|
||||
}
|
||||
}
|
||||
|
||||
KeyEvent keyEvent{ !!bKeyDown, RepeatCount, VirtualKeyCode, VirtualScanCode, UNICODE_NULL, 0 };
|
||||
auto keyEvent = SynthesizeKeyEvent(bKeyDown, RepeatCount, VirtualKeyCode, VirtualScanCode, UNICODE_NULL, 0);
|
||||
|
||||
if (IsCharacterMessage)
|
||||
{
|
||||
// If this is a fake character, zero the scancode.
|
||||
if (lParam & 0x02000000)
|
||||
{
|
||||
keyEvent.SetVirtualScanCode(0);
|
||||
keyEvent.Event.KeyEvent.wVirtualScanCode = 0;
|
||||
}
|
||||
keyEvent.SetActiveModifierKeys(GetControlKeyState(lParam));
|
||||
keyEvent.Event.KeyEvent.dwControlKeyState = GetControlKeyState(lParam);
|
||||
if (Message == WM_CHAR || Message == WM_SYSCHAR)
|
||||
{
|
||||
keyEvent.SetCharData(static_cast<wchar_t>(wParam));
|
||||
}
|
||||
else
|
||||
{
|
||||
keyEvent.SetCharData(L'\0');
|
||||
keyEvent.Event.KeyEvent.uChar.UnicodeChar = static_cast<wchar_t>(wParam);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -212,8 +208,7 @@ void HandleKeyEvent(const HWND hWnd,
|
||||
{
|
||||
return;
|
||||
}
|
||||
keyEvent.SetActiveModifierKeys(ControlKeyState);
|
||||
keyEvent.SetCharData(L'\0');
|
||||
keyEvent.Event.KeyEvent.dwControlKeyState = ControlKeyState;
|
||||
}
|
||||
|
||||
const INPUT_KEY_INFO inputKeyInfo(VirtualKeyCode, ControlKeyState);
|
||||
@ -922,26 +917,12 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
|
||||
break;
|
||||
}
|
||||
|
||||
ULONG EventsWritten = 0;
|
||||
try
|
||||
{
|
||||
auto mouseEvent = std::make_unique<MouseEvent>(
|
||||
MousePosition,
|
||||
ConvertMouseButtonState(ButtonFlags, static_cast<UINT>(wParam)),
|
||||
GetControlKeyState(0),
|
||||
EventFlags);
|
||||
EventsWritten = static_cast<ULONG>(gci.pInputBuffer->Write(std::move(mouseEvent)));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
EventsWritten = 0;
|
||||
}
|
||||
|
||||
if (EventsWritten != 1)
|
||||
{
|
||||
RIPMSG1(RIP_WARNING, "PutInputInBuffer: EventsWritten != 1 (0x%x), 1 expected", EventsWritten);
|
||||
}
|
||||
const auto mouseEvent = SynthesizeMouseEvent(
|
||||
MousePosition,
|
||||
ConvertMouseButtonState(ButtonFlags, static_cast<UINT>(wParam)),
|
||||
GetControlKeyState(0),
|
||||
EventFlags);
|
||||
gci.pInputBuffer->Write(mouseEvent);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@ -212,19 +212,7 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m)
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
for (size_t i = 0; i < cRecords; ++i)
|
||||
{
|
||||
if (outEvents.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
rgRecords[i] = outEvents.front()->ToInputRecord();
|
||||
outEvents.pop_front();
|
||||
}
|
||||
}
|
||||
CATCH_RETURN();
|
||||
std::ranges::copy(outEvents, rgRecords);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
|
||||
@ -135,7 +135,7 @@ bool ConsoleWaitBlock::Notify(const WaitTerminationReason TerminationReason)
|
||||
DWORD dwControlKeyState;
|
||||
auto fIsUnicode = true;
|
||||
|
||||
std::deque<std::unique_ptr<IInputEvent>> outEvents;
|
||||
InputEventQueue outEvents;
|
||||
// TODO: MSFT 14104228 - get rid of this void* and get the data
|
||||
// out of the read wait object properly.
|
||||
void* pOutputData = nullptr;
|
||||
@ -193,15 +193,7 @@ bool ConsoleWaitBlock::Notify(const WaitTerminationReason TerminationReason)
|
||||
|
||||
const auto pRecordBuffer = static_cast<INPUT_RECORD* const>(buffer);
|
||||
a->NumRecords = static_cast<ULONG>(outEvents.size());
|
||||
for (size_t i = 0; i < a->NumRecords; ++i)
|
||||
{
|
||||
if (outEvents.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
pRecordBuffer[i] = outEvents.front()->ToInputRecord();
|
||||
outEvents.pop_front();
|
||||
}
|
||||
std::ranges::copy(outEvents, pRecordBuffer);
|
||||
}
|
||||
else if (API_NUMBER_READCONSOLE == _WaitReplyMessage.msgHeader.ApiNumber)
|
||||
{
|
||||
|
||||
@ -28,9 +28,9 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
virtual ~IInteractDispatch() = default;
|
||||
#pragma warning(pop)
|
||||
|
||||
virtual bool WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inputEvents) = 0;
|
||||
virtual bool WriteInput(const std::span<const INPUT_RECORD>& inputEvents) = 0;
|
||||
|
||||
virtual bool WriteCtrlKey(const KeyEvent& event) = 0;
|
||||
virtual bool WriteCtrlKey(const INPUT_RECORD& event) = 0;
|
||||
|
||||
virtual bool WriteString(const std::wstring_view string) = 0;
|
||||
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
#include "../../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../../interactivity/inc/EventSynthesis.hpp"
|
||||
#include "../../types/inc/Viewport.hpp"
|
||||
#include "../../inc/unicode.hpp"
|
||||
|
||||
using namespace Microsoft::Console::Interactivity;
|
||||
using namespace Microsoft::Console::Types;
|
||||
@ -38,7 +37,7 @@ InteractDispatch::InteractDispatch() :
|
||||
// - inputEvents: a collection of IInputEvents
|
||||
// Return Value:
|
||||
// - True.
|
||||
bool InteractDispatch::WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inputEvents)
|
||||
bool InteractDispatch::WriteInput(const std::span<const INPUT_RECORD>& inputEvents)
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.GetActiveInputBuffer()->Write(inputEvents);
|
||||
@ -52,17 +51,14 @@ bool InteractDispatch::WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inpu
|
||||
// client application.
|
||||
// Arguments:
|
||||
// - event: The key to send to the host.
|
||||
// Return Value:
|
||||
// - True.
|
||||
bool InteractDispatch::WriteCtrlKey(const KeyEvent& event)
|
||||
bool InteractDispatch::WriteCtrlKey(const INPUT_RECORD& event)
|
||||
{
|
||||
HandleGenericKeyEvent(event, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Writes a string of input to the host. The string is converted to keystrokes
|
||||
// that will faithfully represent the input by CharToKeyEvents.
|
||||
// - Writes a string of input to the host.
|
||||
// Arguments:
|
||||
// - string : a string to write to the console.
|
||||
// Return Value:
|
||||
@ -72,15 +68,11 @@ bool InteractDispatch::WriteString(const std::wstring_view string)
|
||||
if (!string.empty())
|
||||
{
|
||||
const auto codepage = _api.GetConsoleOutputCP();
|
||||
std::deque<std::unique_ptr<IInputEvent>> keyEvents;
|
||||
InputEventQueue keyEvents;
|
||||
|
||||
for (const auto& wch : string)
|
||||
{
|
||||
auto convertedEvents = CharToKeyEvents(wch, codepage);
|
||||
|
||||
std::move(convertedEvents.begin(),
|
||||
convertedEvents.end(),
|
||||
std::back_inserter(keyEvents));
|
||||
CharToKeyEvents(wch, codepage, keyEvents);
|
||||
}
|
||||
|
||||
WriteInput(keyEvents);
|
||||
|
||||
@ -25,8 +25,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
public:
|
||||
InteractDispatch();
|
||||
|
||||
bool WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inputEvents) override;
|
||||
bool WriteCtrlKey(const KeyEvent& event) override;
|
||||
bool WriteInput(const std::span<const INPUT_RECORD>& inputEvents) override;
|
||||
bool WriteCtrlKey(const INPUT_RECORD& event) override;
|
||||
bool WriteString(const std::wstring_view string) override;
|
||||
bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
|
||||
const VTParameter parameter1,
|
||||
|
||||
@ -2,9 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <wextestclass.h>
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "../types/inc/IInputEvent.hpp"
|
||||
#include "../terminal/input/terminalInput.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
|
||||
@ -2,14 +2,10 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "../precomp.h"
|
||||
#include <windows.h>
|
||||
#include <wextestclass.h>
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "../../input/terminalInput.hpp"
|
||||
|
||||
#include "../../../interactivity/inc/VtApiRedirection.hpp"
|
||||
#include "../../input/terminalInput.hpp"
|
||||
#include "../types/inc/IInputEvent.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
@ -182,9 +178,8 @@ void InputTest::TerminalInputTests()
|
||||
break;
|
||||
}
|
||||
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
// Send key into object (will trigger callback and verification)
|
||||
VERIFY_ARE_EQUAL(expected, input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been.");
|
||||
}
|
||||
|
||||
Log::Comment(L"Sending every possible VKEY at the input stream for interception during key UP.");
|
||||
@ -198,9 +193,8 @@ void InputTest::TerminalInputTests()
|
||||
irTest.Event.KeyEvent.wVirtualKeyCode = vkey;
|
||||
irTest.Event.KeyEvent.bKeyDown = FALSE;
|
||||
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
// Send key into object (will trigger callback and verification)
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify key was NOT handled.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irTest), L"Verify key was NOT handled.");
|
||||
}
|
||||
|
||||
Log::Comment(L"Verify other types of events are not handled/intercepted.");
|
||||
@ -209,18 +203,15 @@ void InputTest::TerminalInputTests()
|
||||
|
||||
Log::Comment(L"Testing MOUSE_EVENT");
|
||||
irUnhandled.EventType = MOUSE_EVENT;
|
||||
auto inputEvent = IInputEvent::Create(irUnhandled);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify MOUSE_EVENT was NOT handled.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irUnhandled), L"Verify MOUSE_EVENT was NOT handled.");
|
||||
|
||||
Log::Comment(L"Testing WINDOW_BUFFER_SIZE_EVENT");
|
||||
irUnhandled.EventType = WINDOW_BUFFER_SIZE_EVENT;
|
||||
inputEvent = IInputEvent::Create(irUnhandled);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify WINDOW_BUFFER_SIZE_EVENT was NOT handled.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irUnhandled), L"Verify WINDOW_BUFFER_SIZE_EVENT was NOT handled.");
|
||||
|
||||
Log::Comment(L"Testing MENU_EVENT");
|
||||
irUnhandled.EventType = MENU_EVENT;
|
||||
inputEvent = IInputEvent::Create(irUnhandled);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify MENU_EVENT was NOT handled.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irUnhandled), L"Verify MENU_EVENT was NOT handled.");
|
||||
|
||||
// Testing FOCUS_EVENTs is handled by TestFocusEvents
|
||||
}
|
||||
@ -232,40 +223,13 @@ void InputTest::TestFocusEvents()
|
||||
// We're relying on the fact that the INPUT_RECORD version of the ctor is only called by the API
|
||||
TerminalInput input;
|
||||
|
||||
INPUT_RECORD irTest = { 0 };
|
||||
irTest.EventType = FOCUS_EVENT;
|
||||
|
||||
{
|
||||
irTest.Event.FocusEvent.bSetFocus = false;
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled.");
|
||||
}
|
||||
{
|
||||
irTest.Event.FocusEvent.bSetFocus = true;
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled.");
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(false), L"Verify FocusEvent from any other source was NOT handled.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(true), L"Verify FocusEvent from any other source was NOT handled.");
|
||||
|
||||
Log::Comment(L"Enable focus event handling");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(false));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleFocus(true));
|
||||
|
||||
input.SetInputMode(TerminalInput::Mode::FocusEvent, true);
|
||||
|
||||
{
|
||||
irTest.Event.FocusEvent.bSetFocus = false;
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled.");
|
||||
}
|
||||
{
|
||||
irTest.Event.FocusEvent.bSetFocus = true;
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled.");
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[O"), input.HandleFocus(false), L"Verify FocusEvent from any other source was handled.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[I"), input.HandleFocus(true), L"Verify FocusEvent from any other source was handled.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[O"), input.HandleFocus(false));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[I"), input.HandleFocus(true));
|
||||
}
|
||||
|
||||
void InputTest::TerminalInputModifierKeyTests()
|
||||
@ -482,9 +446,8 @@ void InputTest::TerminalInputModifierKeyTests()
|
||||
str[str.size() - 2] = L'1' + (fShift ? 1 : 0) + (fAlt ? 2 : 0) + (fCtrl ? 4 : 0);
|
||||
}
|
||||
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
// Send key into object (will trigger callback and verification)
|
||||
VERIFY_ARE_EQUAL(expected, input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,27 +472,23 @@ void InputTest::TerminalInputNullKeyTests()
|
||||
irTest.Event.KeyEvent.bKeyDown = TRUE;
|
||||
|
||||
// Send key into object (will trigger callback and verification)
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
|
||||
|
||||
vkey = VK_SPACE;
|
||||
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
|
||||
irTest.Event.KeyEvent.wVirtualKeyCode = vkey;
|
||||
irTest.Event.KeyEvent.uChar.UnicodeChar = vkey;
|
||||
inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
|
||||
|
||||
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
|
||||
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
|
||||
irTest.Event.KeyEvent.dwControlKeyState = uiKeystate;
|
||||
inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
|
||||
|
||||
uiKeystate = RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED;
|
||||
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
|
||||
irTest.Event.KeyEvent.dwControlKeyState = uiKeystate;
|
||||
inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
|
||||
}
|
||||
|
||||
static void TestKey(const TerminalInput::OutputType& expected, TerminalInput& input, const unsigned int uiKeystate, const BYTE vkey, const wchar_t wch = 0)
|
||||
@ -545,8 +504,7 @@ static void TestKey(const TerminalInput::OutputType& expected, TerminalInput& in
|
||||
irTest.Event.KeyEvent.uChar.UnicodeChar = wch;
|
||||
|
||||
// Send key into object (will trigger callback and verification)
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(expected, input.HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been.");
|
||||
}
|
||||
|
||||
void InputTest::DifferentModifiersTest()
|
||||
@ -706,23 +664,23 @@ void InputTest::BackarrowKeyModeTest()
|
||||
|
||||
void InputTest::AutoRepeatModeTest()
|
||||
{
|
||||
const auto down = std::make_unique<KeyEvent>(true, 1ui16, 'A', 0ui16, 'A', 0ui16);
|
||||
const auto up = std::make_unique<KeyEvent>(false, 1ui16, 'A', 0ui16, 'A', 0ui16);
|
||||
static constexpr auto down = SynthesizeKeyEvent(true, 1, 'A', 0, 'A', 0);
|
||||
static constexpr auto up = SynthesizeKeyEvent(false, 1, 'A', 0, 'A', 0);
|
||||
TerminalInput input;
|
||||
|
||||
Log::Comment(L"Sending repeating keypresses with DECARM disabled.");
|
||||
|
||||
input.SetInputMode(TerminalInput::Mode::AutoRepeat, false);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get()));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down.get()));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down.get()));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up.get()));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up));
|
||||
|
||||
Log::Comment(L"Sending repeating keypresses with DECARM enabled.");
|
||||
|
||||
input.SetInputMode(TerminalInput::Mode::AutoRepeat, true);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get()));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get()));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down.get()));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up.get()));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up));
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "precomp.h"
|
||||
#include "terminalInput.hpp"
|
||||
|
||||
#include "../types/inc/IInputEvent.hpp"
|
||||
#include "../types/inc/utils.hpp"
|
||||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
@ -6,8 +6,9 @@
|
||||
|
||||
#include <til/unicode.h>
|
||||
|
||||
#include "../../interactivity/inc/VtApiRedirection.hpp"
|
||||
#include "../../inc/unicode.hpp"
|
||||
#include "../../interactivity/inc/VtApiRedirection.hpp"
|
||||
#include "../types/inc/IInputEvent.hpp"
|
||||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
@ -268,45 +269,45 @@ void TerminalInput::ForceDisableWin32InputMode(const bool win32InputMode) noexce
|
||||
_forceDisableWin32InputMode = win32InputMode;
|
||||
}
|
||||
|
||||
static const std::span<const TermKeyMap> _getKeyMapping(const KeyEvent& keyEvent,
|
||||
const bool ansiMode,
|
||||
const bool cursorApplicationMode,
|
||||
const bool keypadApplicationMode) noexcept
|
||||
static std::span<const TermKeyMap> _getKeyMapping(const KEY_EVENT_RECORD& keyEvent, const bool ansiMode, const bool cursorApplicationMode, const bool keypadApplicationMode) noexcept
|
||||
{
|
||||
// Cursor keys: VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN
|
||||
const auto isCursorKey = keyEvent.wVirtualKeyCode >= VK_END && keyEvent.wVirtualKeyCode <= VK_DOWN;
|
||||
|
||||
if (ansiMode)
|
||||
{
|
||||
if (keyEvent.IsCursorKey())
|
||||
if (isCursorKey)
|
||||
{
|
||||
if (cursorApplicationMode)
|
||||
{
|
||||
return { s_cursorKeysApplicationMapping.data(), s_cursorKeysApplicationMapping.size() };
|
||||
return s_cursorKeysApplicationMapping;
|
||||
}
|
||||
else
|
||||
{
|
||||
return { s_cursorKeysNormalMapping.data(), s_cursorKeysNormalMapping.size() };
|
||||
return s_cursorKeysNormalMapping;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (keypadApplicationMode)
|
||||
{
|
||||
return { s_keypadApplicationMapping.data(), s_keypadApplicationMapping.size() };
|
||||
return s_keypadApplicationMapping;
|
||||
}
|
||||
else
|
||||
{
|
||||
return { s_keypadNumericMapping.data(), s_keypadNumericMapping.size() };
|
||||
return s_keypadNumericMapping;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (keyEvent.IsCursorKey())
|
||||
if (isCursorKey)
|
||||
{
|
||||
return { s_cursorKeysVt52Mapping.data(), s_cursorKeysVt52Mapping.size() };
|
||||
return s_cursorKeysVt52Mapping;
|
||||
}
|
||||
else
|
||||
{
|
||||
return { s_keypadVt52Mapping.data(), s_keypadVt52Mapping.size() };
|
||||
return s_keypadVt52Mapping;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -318,12 +319,12 @@ static const std::span<const TermKeyMap> _getKeyMapping(const KeyEvent& keyEvent
|
||||
// - keyMapping - Array of key mappings to search
|
||||
// Return Value:
|
||||
// - Has value if there was a match to a key translation.
|
||||
static std::optional<const TermKeyMap> _searchKeyMapping(const KeyEvent& keyEvent,
|
||||
static std::optional<const TermKeyMap> _searchKeyMapping(const KEY_EVENT_RECORD& keyEvent,
|
||||
std::span<const TermKeyMap> keyMapping) noexcept
|
||||
{
|
||||
for (auto& map : keyMapping)
|
||||
{
|
||||
if (map.vkey == keyEvent.GetVirtualKeyCode())
|
||||
if (map.vkey == keyEvent.wVirtualKeyCode)
|
||||
{
|
||||
// If the mapping has no modifiers set, then it doesn't really care
|
||||
// what the modifiers are on the key. The caller will likely do
|
||||
@ -337,9 +338,9 @@ static std::optional<const TermKeyMap> _searchKeyMapping(const KeyEvent& keyEven
|
||||
// The modifier mapping expects certain modifier keys to be
|
||||
// pressed. Check those as well.
|
||||
modifiersMatch =
|
||||
(WI_IsFlagSet(map.modifiers, SHIFT_PRESSED) == keyEvent.IsShiftPressed()) &&
|
||||
(WI_IsAnyFlagSet(map.modifiers, ALT_PRESSED) == keyEvent.IsAltPressed()) &&
|
||||
(WI_IsAnyFlagSet(map.modifiers, CTRL_PRESSED) == keyEvent.IsCtrlPressed());
|
||||
WI_IsAnyFlagSet(map.modifiers, SHIFT_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED) &&
|
||||
WI_IsAnyFlagSet(map.modifiers, ALT_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) &&
|
||||
WI_IsAnyFlagSet(map.modifiers, CTRL_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
|
||||
}
|
||||
|
||||
if (modifiersMatch)
|
||||
@ -353,16 +354,16 @@ static std::optional<const TermKeyMap> _searchKeyMapping(const KeyEvent& keyEven
|
||||
|
||||
// Searches the s_modifierKeyMapping for a entry corresponding to this key event.
|
||||
// Changes the second to last byte to correspond to the currently pressed modifier keys.
|
||||
TerminalInput::OutputType TerminalInput::_searchWithModifier(const KeyEvent& keyEvent)
|
||||
TerminalInput::OutputType TerminalInput::_searchWithModifier(const KEY_EVENT_RECORD& keyEvent)
|
||||
{
|
||||
if (const auto match = _searchKeyMapping(keyEvent, s_modifierKeyMapping))
|
||||
{
|
||||
const auto& v = match.value();
|
||||
if (!v.sequence.empty())
|
||||
{
|
||||
const auto shift = keyEvent.IsShiftPressed();
|
||||
const auto alt = keyEvent.IsAltPressed();
|
||||
const auto ctrl = keyEvent.IsCtrlPressed();
|
||||
const auto shift = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED);
|
||||
const auto alt = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED);
|
||||
const auto ctrl = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
|
||||
StringType str{ v.sequence };
|
||||
str.at(str.size() - 2) = L'1' + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0);
|
||||
return str;
|
||||
@ -407,12 +408,12 @@ TerminalInput::OutputType TerminalInput::_searchWithModifier(const KeyEvent& key
|
||||
const auto slashVkey = LOBYTE(slashKeyScan);
|
||||
const auto questionMarkVkey = LOBYTE(questionMarkKeyScan);
|
||||
|
||||
const auto ctrl = keyEvent.IsCtrlPressed();
|
||||
const auto alt = keyEvent.IsAltPressed();
|
||||
const auto shift = keyEvent.IsShiftPressed();
|
||||
const auto ctrl = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
|
||||
const auto alt = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED);
|
||||
const auto shift = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED);
|
||||
|
||||
// From the KeyEvent we're translating, synthesize the equivalent VkKeyScan result
|
||||
const auto vkey = keyEvent.GetVirtualKeyCode();
|
||||
const auto vkey = keyEvent.wVirtualKeyCode;
|
||||
const short keyScanFromEvent = vkey |
|
||||
(shift ? 0x100 : 0) |
|
||||
(ctrl ? 0x200 : 0) |
|
||||
@ -474,20 +475,15 @@ TerminalInput::OutputType TerminalInput::MakeOutput(const std::wstring_view& str
|
||||
// Return Value:
|
||||
// - Returns an empty optional if we didn't handle the key event and the caller can opt to handle it in some other way.
|
||||
// - Returns a string if we successfully translated it into a VT input sequence.
|
||||
TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInEvent)
|
||||
TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
{
|
||||
if (!pInEvent)
|
||||
{
|
||||
return MakeUnhandled();
|
||||
}
|
||||
|
||||
// On key presses, prepare to translate to VT compatible sequences
|
||||
if (pInEvent->EventType() != InputEventType::KeyEvent)
|
||||
if (event.EventType != KEY_EVENT)
|
||||
{
|
||||
return MakeUnhandled();
|
||||
}
|
||||
|
||||
auto keyEvent = *static_cast<const KeyEvent* const>(pInEvent);
|
||||
auto keyEvent = event.Event.KeyEvent;
|
||||
|
||||
// GH#4999 - If we're in win32-input mode, skip straight to doing that.
|
||||
// Since this mode handles all types of key events, do nothing else.
|
||||
@ -498,10 +494,10 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
}
|
||||
|
||||
// Check if this key matches the last recorded key code.
|
||||
const auto matchingLastKeyPress = _lastVirtualKeyCode == keyEvent.GetVirtualKeyCode();
|
||||
const auto matchingLastKeyPress = _lastVirtualKeyCode == keyEvent.wVirtualKeyCode;
|
||||
|
||||
// Only need to handle key down. See raw key handler (see RawReadWaitRoutine in stream.cpp)
|
||||
if (!keyEvent.IsKeyDown())
|
||||
if (!keyEvent.bKeyDown)
|
||||
{
|
||||
// If this is a release of the last recorded key press, we can reset that.
|
||||
if (matchingLastKeyPress)
|
||||
@ -519,17 +515,17 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
// the event, otherwise the key press can still end up being submitted.
|
||||
return MakeOutput({});
|
||||
}
|
||||
_lastVirtualKeyCode = keyEvent.GetVirtualKeyCode();
|
||||
_lastVirtualKeyCode = keyEvent.wVirtualKeyCode;
|
||||
|
||||
// The VK_BACK key depends on the state of Backarrow Key mode (DECBKM).
|
||||
// If the mode is set, we should send BS. If reset, we should send DEL.
|
||||
if (keyEvent.GetVirtualKeyCode() == VK_BACK)
|
||||
if (keyEvent.wVirtualKeyCode == VK_BACK)
|
||||
{
|
||||
// The Ctrl modifier reverses the interpretation of DECBKM.
|
||||
const auto backarrowMode = _inputMode.test(Mode::BackarrowKey) != keyEvent.IsCtrlPressed();
|
||||
const auto backarrowMode = _inputMode.test(Mode::BackarrowKey) != WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED);
|
||||
const auto seq = backarrowMode ? L'\x08' : L'\x7f';
|
||||
// The Alt modifier adds an escape prefix.
|
||||
if (keyEvent.IsAltPressed())
|
||||
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED))
|
||||
{
|
||||
return _makeEscapedOutput(seq);
|
||||
}
|
||||
@ -542,7 +538,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
// When the Line Feed mode is set, a VK_RETURN key should send both CR and LF.
|
||||
// When reset, we fall through to the default behavior, which is to send just
|
||||
// CR, or when the Ctrl modifier is pressed, just LF.
|
||||
if (keyEvent.GetVirtualKeyCode() == VK_RETURN && _inputMode.test(Mode::LineFeed))
|
||||
if (keyEvent.wVirtualKeyCode == VK_RETURN && _inputMode.test(Mode::LineFeed))
|
||||
{
|
||||
return MakeOutput(L"\r\n");
|
||||
}
|
||||
@ -550,12 +546,11 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
// Many keyboard layouts have an AltGr key, which makes widely used characters accessible.
|
||||
// For instance on a German keyboard layout "[" is written by pressing AltGr+8.
|
||||
// Furthermore Ctrl+Alt is traditionally treated as an alternative way to AltGr by Windows.
|
||||
// When AltGr is pressed, the caller needs to make sure to send us a pretranslated character in GetCharData().
|
||||
// When AltGr is pressed, the caller needs to make sure to send us a pretranslated character in uChar.UnicodeChar.
|
||||
// --> Strip out the AltGr flags, in order for us to not step into the Alt/Ctrl conditions below.
|
||||
if (keyEvent.IsAltGrPressed())
|
||||
if (WI_AreAllFlagsSet(keyEvent.dwControlKeyState, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED))
|
||||
{
|
||||
keyEvent.DeactivateModifierKey(ModifierKeyState::LeftCtrl);
|
||||
keyEvent.DeactivateModifierKey(ModifierKeyState::RightAlt);
|
||||
WI_ClearAllFlags(keyEvent.dwControlKeyState, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
|
||||
}
|
||||
|
||||
// The Alt modifier initiates a so called "escape sequence".
|
||||
@ -565,16 +560,16 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
// This section in particular handles Alt+Ctrl combinations though.
|
||||
// The Ctrl modifier causes all of the char code's bits except
|
||||
// for the 5 least significant ones to be zeroed out.
|
||||
if (keyEvent.IsAltPressed() && keyEvent.IsCtrlPressed())
|
||||
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED))
|
||||
{
|
||||
const auto ch = keyEvent.GetCharData();
|
||||
const auto vkey = keyEvent.GetVirtualKeyCode();
|
||||
const auto ch = keyEvent.uChar.UnicodeChar;
|
||||
const auto vkey = keyEvent.wVirtualKeyCode;
|
||||
|
||||
// For Alt+Ctrl+Key messages GetCharData() usually returns 0.
|
||||
// For Alt+Ctrl+Key messages uChar.UnicodeChar usually returns 0.
|
||||
// Luckily the numerical values of the ASCII characters and virtual key codes
|
||||
// of <Space> and A-Z, as used below, are numerically identical.
|
||||
// -> Get the char from the virtual key if it's 0.
|
||||
const auto ctrlAltChar = keyEvent.GetCharData() != 0 ? keyEvent.GetCharData() : keyEvent.GetVirtualKeyCode();
|
||||
const auto ctrlAltChar = keyEvent.uChar.UnicodeChar != 0 ? keyEvent.uChar.UnicodeChar : keyEvent.wVirtualKeyCode;
|
||||
|
||||
// Alt+Ctrl acts as a substitute for AltGr on Windows.
|
||||
// For instance using a German keyboard both AltGr+< and Alt+Ctrl+< produce a | (pipe) character.
|
||||
@ -597,7 +592,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
}
|
||||
|
||||
// If a modifier key was pressed, then we need to try and send the modified sequence.
|
||||
if (keyEvent.IsModifierPressed())
|
||||
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, MOD_PRESSED))
|
||||
{
|
||||
if (auto out = _searchWithModifier(keyEvent))
|
||||
{
|
||||
@ -607,9 +602,9 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
|
||||
// This section is similar to the Alt modifier section above,
|
||||
// but handles cases without Ctrl modifiers.
|
||||
if (keyEvent.IsAltPressed() && !keyEvent.IsCtrlPressed() && keyEvent.GetCharData() != 0)
|
||||
if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && !WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED) && keyEvent.uChar.UnicodeChar != 0)
|
||||
{
|
||||
return _makeEscapedOutput(keyEvent.GetCharData());
|
||||
return _makeEscapedOutput(keyEvent.uChar.UnicodeChar);
|
||||
}
|
||||
|
||||
// Pressing the control key causes all bits but the 5 least
|
||||
@ -620,10 +615,10 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
// -> Send a "null input sequence" in that case.
|
||||
// We don't need to handle other kinds of Ctrl combinations,
|
||||
// as we rely on the caller to pretranslate those to characters for us.
|
||||
if (!keyEvent.IsAltPressed() && keyEvent.IsCtrlPressed())
|
||||
if (!WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED))
|
||||
{
|
||||
const auto ch = keyEvent.GetCharData();
|
||||
const auto vkey = keyEvent.GetVirtualKeyCode();
|
||||
const auto ch = keyEvent.uChar.UnicodeChar;
|
||||
const auto vkey = keyEvent.wVirtualKeyCode;
|
||||
|
||||
// Currently, when we're called with Ctrl+@, ch will be 0, since Ctrl+@ equals a null byte.
|
||||
// VkKeyScanW(0) in turn returns the vkey for the null character (ASCII @).
|
||||
@ -639,7 +634,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
if (ch == UNICODE_NULL)
|
||||
{
|
||||
// -> Try to infer the character from the vkey.
|
||||
auto mappedChar = LOWORD(OneCoreSafeMapVirtualKeyW(keyEvent.GetVirtualKeyCode(), MAPVK_VK_TO_CHAR));
|
||||
auto mappedChar = LOWORD(OneCoreSafeMapVirtualKeyW(keyEvent.wVirtualKeyCode, MAPVK_VK_TO_CHAR));
|
||||
if (mappedChar)
|
||||
{
|
||||
// Pressing the control key causes all bits but the 5 least
|
||||
@ -660,9 +655,9 @@ TerminalInput::OutputType TerminalInput::HandleKey(const IInputEvent* const pInE
|
||||
}
|
||||
|
||||
// If all else fails we can finally try to send the character itself if there is any.
|
||||
if (keyEvent.GetCharData() != 0)
|
||||
if (keyEvent.uChar.UnicodeChar != 0)
|
||||
{
|
||||
return _makeCharOutput(keyEvent.GetCharData());
|
||||
return _makeCharOutput(keyEvent.uChar.UnicodeChar);
|
||||
}
|
||||
|
||||
return MakeUnhandled();
|
||||
@ -720,16 +715,16 @@ TerminalInput::OutputType TerminalInput::_makeEscapedOutput(const wchar_t wch)
|
||||
|
||||
// Turns an KEY_EVENT_RECORD into a win32-input-mode VT sequence.
|
||||
// It allows us to send KEY_EVENT_RECORD data losslessly to conhost.
|
||||
TerminalInput::OutputType TerminalInput::_makeWin32Output(const KeyEvent& key)
|
||||
TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD& key)
|
||||
{
|
||||
// .uChar.UnicodeChar must be cast to an integer because we want its numerical value.
|
||||
// Casting the rest to uint16_t as well doesn't hurt because that's MAX_PARAMETER_VALUE anyways.
|
||||
const auto kd = gsl::narrow_cast<uint16_t>(key.IsKeyDown() ? 1 : 0);
|
||||
const auto rc = gsl::narrow_cast<uint16_t>(key.GetRepeatCount());
|
||||
const auto vk = gsl::narrow_cast<uint16_t>(key.GetVirtualKeyCode());
|
||||
const auto sc = gsl::narrow_cast<uint16_t>(key.GetVirtualScanCode());
|
||||
const auto uc = gsl::narrow_cast<uint16_t>(key.GetCharData());
|
||||
const auto cs = gsl::narrow_cast<uint16_t>(key.GetActiveModifierKeys());
|
||||
const auto kd = gsl::narrow_cast<uint16_t>(key.bKeyDown ? 1 : 0);
|
||||
const auto rc = gsl::narrow_cast<uint16_t>(key.wRepeatCount);
|
||||
const auto vk = gsl::narrow_cast<uint16_t>(key.wVirtualKeyCode);
|
||||
const auto sc = gsl::narrow_cast<uint16_t>(key.wVirtualScanCode);
|
||||
const auto uc = gsl::narrow_cast<uint16_t>(key.uChar.UnicodeChar);
|
||||
const auto cs = gsl::narrow_cast<uint16_t>(key.dwControlKeyState);
|
||||
|
||||
// Sequences are formatted as follows:
|
||||
//
|
||||
|
||||
@ -1,22 +1,8 @@
|
||||
/*+
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- terminalInput.hpp
|
||||
|
||||
Abstract:
|
||||
- This serves as an adapter between virtual key input from a user and the virtual terminal sequences that are
|
||||
typically emitted by an xterm-compatible console.
|
||||
|
||||
Author(s):
|
||||
- Michael Niksa (MiNiksa) 30-Oct-2015
|
||||
--*/
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../types/inc/IInputEvent.hpp"
|
||||
|
||||
namespace Microsoft::Console::VirtualTerminal
|
||||
{
|
||||
class TerminalInput final
|
||||
@ -34,7 +20,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
static [[nodiscard]] OutputType MakeUnhandled() noexcept;
|
||||
static [[nodiscard]] OutputType MakeOutput(const std::wstring_view& str);
|
||||
[[nodiscard]] OutputType HandleKey(const IInputEvent* const pInEvent);
|
||||
[[nodiscard]] OutputType HandleKey(const INPUT_RECORD& pInEvent);
|
||||
[[nodiscard]] OutputType HandleFocus(bool focused) const;
|
||||
[[nodiscard]] OutputType HandleMouse(til::point position, unsigned int button, short modifierKeyState, short delta, MouseButtonState state);
|
||||
|
||||
@ -89,8 +75,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
[[nodiscard]] OutputType _makeCharOutput(wchar_t ch);
|
||||
static [[nodiscard]] OutputType _makeEscapedOutput(wchar_t wch);
|
||||
static [[nodiscard]] OutputType _makeWin32Output(const KeyEvent& key);
|
||||
static [[nodiscard]] OutputType _searchWithModifier(const KeyEvent& keyEvent);
|
||||
static [[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key);
|
||||
static [[nodiscard]] OutputType _searchWithModifier(const KEY_EVENT_RECORD& keyEvent);
|
||||
|
||||
#pragma region MouseInputState Management
|
||||
// These methods are defined in mouseInputState.cpp
|
||||
|
||||
@ -132,8 +132,11 @@ bool InputStateMachineEngine::_DoControlCharacter(const wchar_t wch, const bool
|
||||
if (wch == UNICODE_ETX && !writeAlt)
|
||||
{
|
||||
// This is Ctrl+C, which is handled specially by the host.
|
||||
const auto [keyDown, keyUp] = KeyEvent::MakePair(1, 'C', 0, UNICODE_ETX, LEFT_CTRL_PRESSED);
|
||||
success = _pDispatch->WriteCtrlKey(keyDown) && _pDispatch->WriteCtrlKey(keyUp);
|
||||
static constexpr auto keyDown = SynthesizeKeyEvent(true, 1, L'C', 0, UNICODE_ETX, LEFT_CTRL_PRESSED);
|
||||
static constexpr auto keyUp = SynthesizeKeyEvent(false, 1, L'C', 0, UNICODE_ETX, LEFT_CTRL_PRESSED);
|
||||
_pDispatch->WriteCtrlKey(keyDown);
|
||||
_pDispatch->WriteCtrlKey(keyUp);
|
||||
success = true;
|
||||
}
|
||||
else if (wch >= '\x0' && wch < '\x20')
|
||||
{
|
||||
@ -278,10 +281,10 @@ bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view st
|
||||
// similar to TerminalInput::_SendInputSequence
|
||||
if (!string.empty())
|
||||
{
|
||||
std::deque<std::unique_ptr<IInputEvent>> inputEvents;
|
||||
InputEventQueue inputEvents;
|
||||
for (const auto& wch : string)
|
||||
{
|
||||
inputEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
|
||||
inputEvents.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0));
|
||||
}
|
||||
return _pDispatch->WriteInput(inputEvents);
|
||||
}
|
||||
@ -558,7 +561,7 @@ bool InputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
|
||||
void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
|
||||
const short vkey,
|
||||
const DWORD modifierState,
|
||||
std::vector<INPUT_RECORD>& input)
|
||||
InputEventQueue& input)
|
||||
{
|
||||
input.reserve(input.size() + 8);
|
||||
|
||||
@ -570,44 +573,31 @@ void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
|
||||
const auto ctrl = WI_IsFlagSet(modifierState, LEFT_CTRL_PRESSED);
|
||||
const auto alt = WI_IsFlagSet(modifierState, LEFT_ALT_PRESSED);
|
||||
|
||||
INPUT_RECORD next{ 0 };
|
||||
|
||||
auto next = SynthesizeKeyEvent(true, 1, 0, 0, 0, 0);
|
||||
DWORD currentModifiers = 0;
|
||||
|
||||
if (shift)
|
||||
{
|
||||
WI_SetFlag(currentModifiers, SHIFT_PRESSED);
|
||||
next.EventType = KEY_EVENT;
|
||||
next.Event.KeyEvent.bKeyDown = TRUE;
|
||||
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
|
||||
next.Event.KeyEvent.wRepeatCount = 1;
|
||||
next.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT;
|
||||
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_SHIFT, MAPVK_VK_TO_VSC));
|
||||
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
|
||||
input.push_back(next);
|
||||
}
|
||||
if (alt)
|
||||
{
|
||||
WI_SetFlag(currentModifiers, LEFT_ALT_PRESSED);
|
||||
next.EventType = KEY_EVENT;
|
||||
next.Event.KeyEvent.bKeyDown = TRUE;
|
||||
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
|
||||
next.Event.KeyEvent.wRepeatCount = 1;
|
||||
next.Event.KeyEvent.wVirtualKeyCode = VK_MENU;
|
||||
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_MENU, MAPVK_VK_TO_VSC));
|
||||
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
|
||||
input.push_back(next);
|
||||
}
|
||||
if (ctrl)
|
||||
{
|
||||
WI_SetFlag(currentModifiers, LEFT_CTRL_PRESSED);
|
||||
next.EventType = KEY_EVENT;
|
||||
next.Event.KeyEvent.bKeyDown = TRUE;
|
||||
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
|
||||
next.Event.KeyEvent.wRepeatCount = 1;
|
||||
next.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL;
|
||||
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_CONTROL, MAPVK_VK_TO_VSC));
|
||||
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
|
||||
input.push_back(next);
|
||||
}
|
||||
|
||||
@ -616,40 +606,30 @@ void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
|
||||
// through on the KeyPress.
|
||||
_GetSingleKeypress(wch, vkey, modifierState, input);
|
||||
|
||||
next.Event.KeyEvent.bKeyDown = FALSE;
|
||||
|
||||
if (ctrl)
|
||||
{
|
||||
WI_ClearFlag(currentModifiers, LEFT_CTRL_PRESSED);
|
||||
next.EventType = KEY_EVENT;
|
||||
next.Event.KeyEvent.bKeyDown = FALSE;
|
||||
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
|
||||
next.Event.KeyEvent.wRepeatCount = 1;
|
||||
next.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL;
|
||||
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_CONTROL, MAPVK_VK_TO_VSC));
|
||||
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
|
||||
input.push_back(next);
|
||||
}
|
||||
if (alt)
|
||||
{
|
||||
WI_ClearFlag(currentModifiers, LEFT_ALT_PRESSED);
|
||||
next.EventType = KEY_EVENT;
|
||||
next.Event.KeyEvent.bKeyDown = FALSE;
|
||||
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
|
||||
next.Event.KeyEvent.wRepeatCount = 1;
|
||||
next.Event.KeyEvent.wVirtualKeyCode = VK_MENU;
|
||||
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_MENU, MAPVK_VK_TO_VSC));
|
||||
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
|
||||
input.push_back(next);
|
||||
}
|
||||
if (shift)
|
||||
{
|
||||
WI_ClearFlag(currentModifiers, SHIFT_PRESSED);
|
||||
next.EventType = KEY_EVENT;
|
||||
next.Event.KeyEvent.bKeyDown = FALSE;
|
||||
next.Event.KeyEvent.dwControlKeyState = currentModifiers;
|
||||
next.Event.KeyEvent.wRepeatCount = 1;
|
||||
next.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT;
|
||||
next.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(VK_SHIFT, MAPVK_VK_TO_VSC));
|
||||
next.Event.KeyEvent.uChar.UnicodeChar = 0x0;
|
||||
input.push_back(next);
|
||||
}
|
||||
}
|
||||
@ -668,21 +648,14 @@ void InputStateMachineEngine::_GenerateWrappedSequence(const wchar_t wch,
|
||||
void InputStateMachineEngine::_GetSingleKeypress(const wchar_t wch,
|
||||
const short vkey,
|
||||
const DWORD modifierState,
|
||||
std::vector<INPUT_RECORD>& input)
|
||||
InputEventQueue& input)
|
||||
{
|
||||
input.reserve(input.size() + 2);
|
||||
|
||||
INPUT_RECORD rec;
|
||||
const auto sc = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(vkey, MAPVK_VK_TO_VSC));
|
||||
auto rec = SynthesizeKeyEvent(true, 1, vkey, sc, wch, modifierState);
|
||||
|
||||
rec.EventType = KEY_EVENT;
|
||||
rec.Event.KeyEvent.bKeyDown = TRUE;
|
||||
rec.Event.KeyEvent.dwControlKeyState = modifierState;
|
||||
rec.Event.KeyEvent.wRepeatCount = 1;
|
||||
rec.Event.KeyEvent.wVirtualKeyCode = vkey;
|
||||
rec.Event.KeyEvent.wVirtualScanCode = gsl::narrow_cast<WORD>(OneCoreSafeMapVirtualKeyW(vkey, MAPVK_VK_TO_VSC));
|
||||
rec.Event.KeyEvent.uChar.UnicodeChar = wch;
|
||||
input.push_back(rec);
|
||||
|
||||
rec.Event.KeyEvent.bKeyDown = FALSE;
|
||||
input.push_back(rec);
|
||||
}
|
||||
@ -701,10 +674,8 @@ void InputStateMachineEngine::_GetSingleKeypress(const wchar_t wch,
|
||||
bool InputStateMachineEngine::_WriteSingleKey(const wchar_t wch, const short vkey, const DWORD modifierState)
|
||||
{
|
||||
// At most 8 records - 2 for each of shift,ctrl,alt up and down, and 2 for the actual key up and down.
|
||||
std::vector<INPUT_RECORD> input;
|
||||
_GenerateWrappedSequence(wch, vkey, modifierState, input);
|
||||
auto inputEvents = IInputEvent::Create(std::span{ input });
|
||||
|
||||
InputEventQueue inputEvents;
|
||||
_GenerateWrappedSequence(wch, vkey, modifierState, inputEvents);
|
||||
return _pDispatch->WriteInput(inputEvents);
|
||||
}
|
||||
|
||||
@ -734,18 +705,8 @@ bool InputStateMachineEngine::_WriteSingleKey(const short vkey, const DWORD modi
|
||||
// - true iff we successfully wrote the keypress to the input callback.
|
||||
bool InputStateMachineEngine::_WriteMouseEvent(const til::point uiPos, const DWORD buttonState, const DWORD controlKeyState, const DWORD eventFlags)
|
||||
{
|
||||
INPUT_RECORD rgInput;
|
||||
rgInput.EventType = MOUSE_EVENT;
|
||||
rgInput.Event.MouseEvent.dwMousePosition.X = ::base::saturated_cast<short>(uiPos.x);
|
||||
rgInput.Event.MouseEvent.dwMousePosition.Y = ::base::saturated_cast<short>(uiPos.y);
|
||||
rgInput.Event.MouseEvent.dwButtonState = buttonState;
|
||||
rgInput.Event.MouseEvent.dwControlKeyState = controlKeyState;
|
||||
rgInput.Event.MouseEvent.dwEventFlags = eventFlags;
|
||||
|
||||
// pack and write input record
|
||||
// 1 record - the modifiers don't get their own events
|
||||
auto inputEvents = IInputEvent::Create(std::span{ &rgInput, 1 });
|
||||
return _pDispatch->WriteInput(inputEvents);
|
||||
const auto rgInput = SynthesizeMouseEvent(uiPos, buttonState, controlKeyState, eventFlags);
|
||||
return _pDispatch->WriteInput({ &rgInput, 1 });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@ -1127,7 +1088,7 @@ bool InputStateMachineEngine::_GetWindowManipulationType(const std::span<const s
|
||||
// - parameters: the list of numbers to parse into values for the KeyEvent.
|
||||
// Return Value:
|
||||
// - The deserialized KeyEvent.
|
||||
KeyEvent InputStateMachineEngine::_GenerateWin32Key(const VTParameters parameters)
|
||||
INPUT_RECORD InputStateMachineEngine::_GenerateWin32Key(const VTParameters& parameters)
|
||||
{
|
||||
// Sequences are formatted as follows:
|
||||
//
|
||||
@ -1140,13 +1101,11 @@ KeyEvent InputStateMachineEngine::_GenerateWin32Key(const VTParameters parameter
|
||||
// Kd: the value of bKeyDown - either a '0' or '1'. If omitted, defaults to '0'.
|
||||
// Cs: the value of dwControlKeyState - any number. If omitted, defaults to '0'.
|
||||
// Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'.
|
||||
|
||||
auto key = KeyEvent();
|
||||
key.SetVirtualKeyCode(::base::saturated_cast<WORD>(parameters.at(0).value_or(0)));
|
||||
key.SetVirtualScanCode(::base::saturated_cast<WORD>(parameters.at(1).value_or(0)));
|
||||
key.SetCharData(::base::saturated_cast<wchar_t>(parameters.at(2).value_or(0)));
|
||||
key.SetKeyDown(parameters.at(3).value_or(0));
|
||||
key.SetActiveModifierKeys(::base::saturated_cast<DWORD>(parameters.at(4).value_or(0)));
|
||||
key.SetRepeatCount(::base::saturated_cast<WORD>(parameters.at(5).value_or(1)));
|
||||
return key;
|
||||
return SynthesizeKeyEvent(
|
||||
parameters.at(3).value_or(0),
|
||||
::base::saturated_cast<uint16_t>(parameters.at(5).value_or(1)),
|
||||
::base::saturated_cast<uint16_t>(parameters.at(0).value_or(0)),
|
||||
::base::saturated_cast<uint16_t>(parameters.at(1).value_or(0)),
|
||||
::base::saturated_cast<wchar_t>(parameters.at(2).value_or(0)),
|
||||
::base::saturated_cast<uint32_t>(parameters.at(4).value_or(0)));
|
||||
}
|
||||
|
||||
@ -197,17 +197,17 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void _GenerateWrappedSequence(const wchar_t wch,
|
||||
const short vkey,
|
||||
const DWORD modifierState,
|
||||
std::vector<INPUT_RECORD>& input);
|
||||
InputEventQueue& input);
|
||||
|
||||
void _GetSingleKeypress(const wchar_t wch,
|
||||
const short vkey,
|
||||
const DWORD modifierState,
|
||||
std::vector<INPUT_RECORD>& input);
|
||||
InputEventQueue& input);
|
||||
|
||||
bool _GetWindowManipulationType(const std::span<const size_t> parameters,
|
||||
unsigned int& function) const noexcept;
|
||||
|
||||
KeyEvent _GenerateWin32Key(const VTParameters parameters);
|
||||
static INPUT_RECORD _GenerateWin32Key(const VTParameters& parameters);
|
||||
|
||||
bool _DoControlCharacter(const wchar_t wch, const bool writeAlt);
|
||||
|
||||
|
||||
@ -75,11 +75,10 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void RoundtripTerminalInputCallback(_In_ std::deque<std::unique_ptr<IInputEvent>>& inEvents)
|
||||
void RoundtripTerminalInputCallback(_In_ const std::span<const INPUT_RECORD>& inputRecords)
|
||||
{
|
||||
// Take all the characters out of the input records here, and put them into
|
||||
// the input state machine.
|
||||
auto inputRecords = IInputEvent::ToInputRecords(inEvents);
|
||||
std::wstring vtseq = L"";
|
||||
for (auto& inRec : inputRecords)
|
||||
{
|
||||
@ -96,10 +95,8 @@ public:
|
||||
Log::Comment(L"String processed");
|
||||
}
|
||||
|
||||
void TestInputCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents)
|
||||
void TestInputCallback(const std::span<const INPUT_RECORD>& records)
|
||||
{
|
||||
auto records = IInputEvent::ToInputRecords(inEvents);
|
||||
|
||||
// This callback doesn't work super well for the Ctrl+C iteration of the
|
||||
// C0Test. For ^C, we always send a keydown and a key up event, however,
|
||||
// both calls to WriteCtrlKey happen in one single call to
|
||||
@ -146,10 +143,8 @@ public:
|
||||
vExpectedInput.clear();
|
||||
}
|
||||
|
||||
void TestInputStringCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents)
|
||||
void TestInputStringCallback(const std::span<const INPUT_RECORD>& records)
|
||||
{
|
||||
auto records = IInputEvent::ToInputRecords(inEvents);
|
||||
|
||||
for (auto expected : vExpectedInput)
|
||||
{
|
||||
Log::Comment(
|
||||
@ -211,9 +206,9 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest
|
||||
|
||||
TestState testState;
|
||||
|
||||
void RoundtripTerminalInputCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents);
|
||||
void TestInputCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents);
|
||||
void TestInputStringCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents);
|
||||
void RoundtripTerminalInputCallback(const std::span<const INPUT_RECORD>& inEvents);
|
||||
void TestInputCallback(const std::span<const INPUT_RECORD>& inEvents);
|
||||
void TestInputStringCallback(const std::span<const INPUT_RECORD>& inEvents);
|
||||
std::wstring GenerateSgrMouseSequence(const CsiMouseButtonCodes button,
|
||||
const unsigned short modifiers,
|
||||
const til::point position,
|
||||
@ -318,11 +313,11 @@ void InputEngineTest::VerifyExpectedInputDrained()
|
||||
class Microsoft::Console::VirtualTerminal::TestInteractDispatch final : public IInteractDispatch
|
||||
{
|
||||
public:
|
||||
TestInteractDispatch(_In_ std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> pfn,
|
||||
TestInteractDispatch(_In_ std::function<void(const std::span<const INPUT_RECORD>&)> pfn,
|
||||
_In_ TestState* testState);
|
||||
virtual bool WriteInput(_In_ std::deque<std::unique_ptr<IInputEvent>>& inputEvents) override;
|
||||
virtual bool WriteInput(_In_ const std::span<const INPUT_RECORD>& inputEvents) override;
|
||||
|
||||
virtual bool WriteCtrlKey(const KeyEvent& event) override;
|
||||
virtual bool WriteCtrlKey(const INPUT_RECORD& event) override;
|
||||
virtual bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
|
||||
const VTParameter parameter1,
|
||||
const VTParameter parameter2) override; // DTTERM_WindowManipulation
|
||||
@ -336,29 +331,27 @@ public:
|
||||
virtual bool FocusChanged(const bool focused) const override;
|
||||
|
||||
private:
|
||||
std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> _pfnWriteInputCallback;
|
||||
std::function<void(const std::span<const INPUT_RECORD>&)> _pfnWriteInputCallback;
|
||||
TestState* _testState; // non-ownership pointer
|
||||
};
|
||||
|
||||
TestInteractDispatch::TestInteractDispatch(_In_ std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> pfn,
|
||||
TestInteractDispatch::TestInteractDispatch(_In_ std::function<void(const std::span<const INPUT_RECORD>&)> pfn,
|
||||
_In_ TestState* testState) :
|
||||
_pfnWriteInputCallback(pfn),
|
||||
_testState(testState)
|
||||
{
|
||||
}
|
||||
|
||||
bool TestInteractDispatch::WriteInput(_In_ std::deque<std::unique_ptr<IInputEvent>>& inputEvents)
|
||||
bool TestInteractDispatch::WriteInput(_In_ const std::span<const INPUT_RECORD>& inputEvents)
|
||||
{
|
||||
_pfnWriteInputCallback(inputEvents);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestInteractDispatch::WriteCtrlKey(const KeyEvent& event)
|
||||
bool TestInteractDispatch::WriteCtrlKey(const INPUT_RECORD& event)
|
||||
{
|
||||
VERIFY_IS_TRUE(_testState->_expectSendCtrlC);
|
||||
std::deque<std::unique_ptr<IInputEvent>> inputEvents;
|
||||
inputEvents.push_back(std::make_unique<KeyEvent>(event));
|
||||
return WriteInput(inputEvents);
|
||||
return WriteInput({ &event, 1 });
|
||||
}
|
||||
|
||||
bool TestInteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulationType function,
|
||||
@ -374,16 +367,13 @@ bool TestInteractDispatch::WindowManipulation(const DispatchTypes::WindowManipul
|
||||
|
||||
bool TestInteractDispatch::WriteString(const std::wstring_view string)
|
||||
{
|
||||
std::deque<std::unique_ptr<IInputEvent>> keyEvents;
|
||||
InputEventQueue keyEvents;
|
||||
|
||||
for (const auto& wch : string)
|
||||
{
|
||||
// We're forcing the translation to CP_USA, so that it'll be constant
|
||||
// regardless of the CP the test is running in
|
||||
auto convertedEvents = Microsoft::Console::Interactivity::CharToKeyEvents(wch, CP_USA);
|
||||
std::move(convertedEvents.begin(),
|
||||
convertedEvents.end(),
|
||||
std::back_inserter(keyEvents));
|
||||
Microsoft::Console::Interactivity::CharToKeyEvents(wch, CP_USA, keyEvents);
|
||||
}
|
||||
|
||||
return WriteInput(keyEvents);
|
||||
@ -966,12 +956,12 @@ void InputEngineTest::AltIntermediateTest()
|
||||
// Create the callback that's fired when the state machine wants to write
|
||||
// input. We'll take the events and put them straight into the
|
||||
// TerminalInput.
|
||||
auto pfnInputStateMachineCallback = [&](std::deque<std::unique_ptr<IInputEvent>>& inEvents) {
|
||||
auto pfnInputStateMachineCallback = [&](const std::span<const INPUT_RECORD>& inEvents) {
|
||||
for (auto& ev : inEvents)
|
||||
{
|
||||
if (const auto out = terminalInput.HandleKey(ev.get()))
|
||||
if (const auto str = terminalInput.HandleKey(ev))
|
||||
{
|
||||
translation.append(*out);
|
||||
translation.append(*str);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1474,73 +1464,73 @@ void InputEngineTest::TestWin32InputParsing()
|
||||
|
||||
{
|
||||
std::vector<VTParameter> params{ 1 };
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
|
||||
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
|
||||
VERIFY_ARE_EQUAL(0, key.GetVirtualScanCode());
|
||||
VERIFY_ARE_EQUAL(L'\0', key.GetCharData());
|
||||
VERIFY_ARE_EQUAL(false, key.IsKeyDown());
|
||||
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
|
||||
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
|
||||
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
|
||||
VERIFY_ARE_EQUAL(0, key.wVirtualScanCode);
|
||||
VERIFY_ARE_EQUAL(L'\0', key.uChar.UnicodeChar);
|
||||
VERIFY_ARE_EQUAL(FALSE, key.bKeyDown);
|
||||
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
|
||||
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
|
||||
}
|
||||
{
|
||||
std::vector<VTParameter> params{ 1, 2 };
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
|
||||
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
|
||||
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
|
||||
VERIFY_ARE_EQUAL(L'\0', key.GetCharData());
|
||||
VERIFY_ARE_EQUAL(false, key.IsKeyDown());
|
||||
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
|
||||
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
|
||||
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
|
||||
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
|
||||
VERIFY_ARE_EQUAL(L'\0', key.uChar.UnicodeChar);
|
||||
VERIFY_ARE_EQUAL(FALSE, key.bKeyDown);
|
||||
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
|
||||
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
|
||||
}
|
||||
{
|
||||
std::vector<VTParameter> params{ 1, 2, 3 };
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
|
||||
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
|
||||
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
|
||||
VERIFY_ARE_EQUAL(false, key.IsKeyDown());
|
||||
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
|
||||
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
|
||||
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
|
||||
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
|
||||
VERIFY_ARE_EQUAL(FALSE, key.bKeyDown);
|
||||
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
|
||||
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
|
||||
}
|
||||
{
|
||||
std::vector<VTParameter> params{ 1, 2, 3, 4 };
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
|
||||
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
|
||||
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
|
||||
VERIFY_ARE_EQUAL(true, key.IsKeyDown());
|
||||
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
|
||||
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
|
||||
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
|
||||
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
|
||||
VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
|
||||
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
|
||||
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
|
||||
}
|
||||
{
|
||||
std::vector<VTParameter> params{ 1, 2, 3, 1 };
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
|
||||
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
|
||||
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
|
||||
VERIFY_ARE_EQUAL(true, key.IsKeyDown());
|
||||
VERIFY_ARE_EQUAL(0u, key.GetActiveModifierKeys());
|
||||
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
|
||||
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
|
||||
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
|
||||
VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
|
||||
VERIFY_ARE_EQUAL(0u, key.dwControlKeyState);
|
||||
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
|
||||
}
|
||||
{
|
||||
std::vector<VTParameter> params{ 1, 2, 3, 4, 5 };
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
|
||||
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
|
||||
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
|
||||
VERIFY_ARE_EQUAL(true, key.IsKeyDown());
|
||||
VERIFY_ARE_EQUAL(0x5u, key.GetActiveModifierKeys());
|
||||
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
|
||||
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
|
||||
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
|
||||
VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
|
||||
VERIFY_ARE_EQUAL(0x5u, key.dwControlKeyState);
|
||||
VERIFY_ARE_EQUAL(1, key.wRepeatCount);
|
||||
}
|
||||
{
|
||||
std::vector<VTParameter> params{ 1, 2, 3, 4, 5, 6 };
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() });
|
||||
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
|
||||
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
|
||||
VERIFY_ARE_EQUAL(true, key.IsKeyDown());
|
||||
VERIFY_ARE_EQUAL(0x5u, key.GetActiveModifierKeys());
|
||||
VERIFY_ARE_EQUAL(6, key.GetRepeatCount());
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), params.size() }).Event.KeyEvent;
|
||||
VERIFY_ARE_EQUAL(1, key.wVirtualKeyCode);
|
||||
VERIFY_ARE_EQUAL(2, key.wVirtualScanCode);
|
||||
VERIFY_ARE_EQUAL(L'\x03', key.uChar.UnicodeChar);
|
||||
VERIFY_ARE_EQUAL(TRUE, key.bKeyDown);
|
||||
VERIFY_ARE_EQUAL(0x5u, key.dwControlKeyState);
|
||||
VERIFY_ARE_EQUAL(6, key.wRepeatCount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1580,26 +1570,26 @@ void InputEngineTest::TestWin32InputOptionals()
|
||||
provideRepeatCount ? 6 : 0,
|
||||
};
|
||||
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), numParams });
|
||||
auto key = engine->_GenerateWin32Key({ params.data(), numParams }).Event.KeyEvent;
|
||||
VERIFY_ARE_EQUAL((provideVirtualKeyCode && numParams > 0) ? 1 : 0,
|
||||
key.GetVirtualKeyCode());
|
||||
key.wVirtualKeyCode);
|
||||
VERIFY_ARE_EQUAL((provideVirtualScanCode && numParams > 1) ? 2 : 0,
|
||||
key.GetVirtualScanCode());
|
||||
key.wVirtualScanCode);
|
||||
VERIFY_ARE_EQUAL((provideCharData && numParams > 2) ? L'\x03' : L'\0',
|
||||
key.GetCharData());
|
||||
VERIFY_ARE_EQUAL((provideKeyDown && numParams > 3) ? true : false,
|
||||
key.IsKeyDown());
|
||||
key.uChar.UnicodeChar);
|
||||
VERIFY_ARE_EQUAL((provideKeyDown && numParams > 3) ? TRUE : FALSE,
|
||||
key.bKeyDown);
|
||||
VERIFY_ARE_EQUAL((provideActiveModifierKeys && numParams > 4) ? 5u : 0u,
|
||||
key.GetActiveModifierKeys());
|
||||
key.dwControlKeyState);
|
||||
if (numParams == 6)
|
||||
{
|
||||
VERIFY_ARE_EQUAL((provideRepeatCount) ? 6 : 0,
|
||||
key.GetRepeatCount());
|
||||
key.wRepeatCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_ARE_EQUAL((provideRepeatCount && numParams > 5) ? 6 : 1,
|
||||
key.GetRepeatCount());
|
||||
key.wRepeatCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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")";
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -1,553 +1,89 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
- IInputEvent.hpp
|
||||
|
||||
Abstract:
|
||||
- Internal representation of public INPUT_RECORD struct.
|
||||
|
||||
Author:
|
||||
- Austin Diviness (AustDi) 18-Aug-2017
|
||||
--*/
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <wil/common.h>
|
||||
#include <wil/resource.h>
|
||||
|
||||
#ifndef ALTNUMPAD_BIT
|
||||
// from winconp.h
|
||||
#define ALTNUMPAD_BIT 0x04000000 // AltNumpad OEM char (copied from ntuser/inc/kbd.h)
|
||||
#endif
|
||||
|
||||
#include <wtypes.h>
|
||||
|
||||
#include <unordered_set>
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <ostream>
|
||||
|
||||
enum class InputEventType
|
||||
{
|
||||
KeyEvent,
|
||||
MouseEvent,
|
||||
WindowBufferSizeEvent,
|
||||
MenuEvent,
|
||||
FocusEvent
|
||||
};
|
||||
|
||||
class IInputEvent
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<IInputEvent> Create(const INPUT_RECORD& record);
|
||||
static std::deque<std::unique_ptr<IInputEvent>> Create(std::span<const INPUT_RECORD> records);
|
||||
static std::deque<std::unique_ptr<IInputEvent>> Create(const std::deque<INPUT_RECORD>& records);
|
||||
|
||||
static std::vector<INPUT_RECORD> ToInputRecords(const std::deque<std::unique_ptr<IInputEvent>>& events);
|
||||
|
||||
virtual ~IInputEvent() = 0;
|
||||
IInputEvent() = default;
|
||||
IInputEvent(const IInputEvent&) = default;
|
||||
IInputEvent(IInputEvent&&) = default;
|
||||
IInputEvent& operator=(const IInputEvent&) & = default;
|
||||
IInputEvent& operator=(IInputEvent&&) & = default;
|
||||
|
||||
virtual INPUT_RECORD ToInputRecord() const noexcept = 0;
|
||||
|
||||
virtual InputEventType EventType() const noexcept = 0;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend std::wostream& operator<<(std::wostream& stream, const IInputEvent* const pEvent);
|
||||
#endif
|
||||
};
|
||||
|
||||
inline IInputEvent::~IInputEvent() = default;
|
||||
|
||||
using InputEventQueue = std::deque<std::unique_ptr<IInputEvent>>;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
std::wostream& operator<<(std::wostream& stream, const IInputEvent* pEvent);
|
||||
#endif
|
||||
#include <til/small_vector.h>
|
||||
|
||||
#define ALT_PRESSED (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)
|
||||
#define CTRL_PRESSED (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)
|
||||
#define MOD_PRESSED (SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED)
|
||||
|
||||
// Note taken from VkKeyScan docs (https://msdn.microsoft.com/en-us/library/windows/desktop/ms646329(v=vs.85).aspx):
|
||||
// For keyboard layouts that use the right-hand ALT key as a shift key
|
||||
// (for example, the French keyboard layout), the shift state is
|
||||
// represented by the value 6, because the right-hand ALT key is
|
||||
// converted internally into CTRL+ALT.
|
||||
struct VkKeyScanModState
|
||||
{
|
||||
static const byte None = 0;
|
||||
static const byte ShiftPressed = 1;
|
||||
static const byte CtrlPressed = 2;
|
||||
static const byte ShiftAndCtrlPressed = ShiftPressed | CtrlPressed;
|
||||
static const byte AltPressed = 4;
|
||||
static const byte ShiftAndAltPressed = ShiftPressed | AltPressed;
|
||||
static const byte CtrlAndAltPressed = CtrlPressed | AltPressed;
|
||||
static const byte ModPressed = ShiftPressed | CtrlPressed | AltPressed;
|
||||
};
|
||||
using InputEventQueue = til::small_vector<INPUT_RECORD, 16>;
|
||||
|
||||
enum class ModifierKeyState
|
||||
// The following abstractions exist to hopefully make it easier to migrate
|
||||
// to a different underlying InputEvent type if we ever choose to do so.
|
||||
// (As unlikely as that is, given that the main user is the console API which will always use INPUT_RECORD.)
|
||||
constexpr INPUT_RECORD SynthesizeKeyEvent(bool bKeyDown, uint16_t wRepeatCount, uint16_t wVirtualKeyCode, uint16_t wVirtualScanCode, wchar_t UnicodeChar, uint32_t dwControlKeyState)
|
||||
{
|
||||
RightAlt,
|
||||
LeftAlt,
|
||||
RightCtrl,
|
||||
LeftCtrl,
|
||||
Shift,
|
||||
NumLock,
|
||||
ScrollLock,
|
||||
CapsLock,
|
||||
EnhancedKey,
|
||||
NlsDbcsChar,
|
||||
NlsAlphanumeric,
|
||||
NlsKatakana,
|
||||
NlsHiragana,
|
||||
NlsRoman,
|
||||
NlsImeConversion,
|
||||
AltNumpad,
|
||||
NlsImeDisable,
|
||||
ENUM_COUNT // must be the last element in the enum class
|
||||
};
|
||||
|
||||
std::unordered_set<ModifierKeyState> FromVkKeyScan(const short vkKeyScanFlags);
|
||||
std::unordered_set<ModifierKeyState> FromConsoleControlKeyFlags(const DWORD flags);
|
||||
DWORD ToConsoleControlKeyFlag(const ModifierKeyState modifierKey) noexcept;
|
||||
|
||||
class KeyEvent : public IInputEvent
|
||||
{
|
||||
public:
|
||||
enum class Modifiers : DWORD
|
||||
{
|
||||
None = 0,
|
||||
RightAlt = RIGHT_ALT_PRESSED,
|
||||
LeftAlt = LEFT_ALT_PRESSED,
|
||||
RightCtrl = RIGHT_CTRL_PRESSED,
|
||||
LeftCtrl = LEFT_CTRL_PRESSED,
|
||||
Shift = SHIFT_PRESSED,
|
||||
NumLock = NUMLOCK_ON,
|
||||
ScrollLock = SCROLLLOCK_ON,
|
||||
CapsLock = CAPSLOCK_ON,
|
||||
EnhancedKey = ENHANCED_KEY,
|
||||
DbcsChar = NLS_DBCSCHAR,
|
||||
Alphanumeric = NLS_ALPHANUMERIC,
|
||||
Katakana = NLS_KATAKANA,
|
||||
Hiragana = NLS_HIRAGANA,
|
||||
Roman = NLS_ROMAN,
|
||||
ImeConvert = NLS_IME_CONVERSION,
|
||||
AltNumpad = ALTNUMPAD_BIT,
|
||||
ImeDisable = NLS_IME_DISABLE
|
||||
return INPUT_RECORD{
|
||||
.EventType = KEY_EVENT,
|
||||
.Event = {
|
||||
.KeyEvent = {
|
||||
.bKeyDown = bKeyDown,
|
||||
.wRepeatCount = wRepeatCount,
|
||||
.wVirtualKeyCode = wVirtualKeyCode,
|
||||
.wVirtualScanCode = wVirtualScanCode,
|
||||
.uChar = { .UnicodeChar = UnicodeChar },
|
||||
.dwControlKeyState = dwControlKeyState,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
constexpr KeyEvent(const KEY_EVENT_RECORD& record) :
|
||||
_keyDown{ !!record.bKeyDown },
|
||||
_repeatCount{ record.wRepeatCount },
|
||||
_virtualKeyCode{ record.wVirtualKeyCode },
|
||||
_virtualScanCode{ record.wVirtualScanCode },
|
||||
_charData{ record.uChar.UnicodeChar },
|
||||
_activeModifierKeys{ record.dwControlKeyState }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr KeyEvent(const bool keyDown,
|
||||
const WORD repeatCount,
|
||||
const WORD virtualKeyCode,
|
||||
const WORD virtualScanCode,
|
||||
const wchar_t charData,
|
||||
const DWORD activeModifierKeys) :
|
||||
_keyDown{ keyDown },
|
||||
_repeatCount{ repeatCount },
|
||||
_virtualKeyCode{ virtualKeyCode },
|
||||
_virtualScanCode{ virtualScanCode },
|
||||
_charData{ charData },
|
||||
_activeModifierKeys{ activeModifierKeys }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr KeyEvent() noexcept :
|
||||
_keyDown{ 0 },
|
||||
_repeatCount{ 0 },
|
||||
_virtualKeyCode{ 0 },
|
||||
_virtualScanCode{ 0 },
|
||||
_charData{ 0 },
|
||||
_activeModifierKeys{ 0 }
|
||||
{
|
||||
}
|
||||
|
||||
static std::pair<KeyEvent, KeyEvent> MakePair(
|
||||
const WORD repeatCount,
|
||||
const WORD virtualKeyCode,
|
||||
const WORD virtualScanCode,
|
||||
const wchar_t charData,
|
||||
const DWORD activeModifierKeys)
|
||||
{
|
||||
return std::make_pair<KeyEvent, KeyEvent>(
|
||||
{ true,
|
||||
repeatCount,
|
||||
virtualKeyCode,
|
||||
virtualScanCode,
|
||||
charData,
|
||||
activeModifierKeys },
|
||||
{ false,
|
||||
repeatCount,
|
||||
virtualKeyCode,
|
||||
virtualScanCode,
|
||||
charData,
|
||||
activeModifierKeys });
|
||||
}
|
||||
|
||||
~KeyEvent();
|
||||
KeyEvent(const KeyEvent&) = default;
|
||||
KeyEvent(KeyEvent&&) = default;
|
||||
// For these two operators, there seems to be a bug in the compiler:
|
||||
// See https://stackoverflow.com/a/60206505/1481137
|
||||
// > C.128 applies only to virtual member functions, but operator= is not
|
||||
// > virtual in your base class and neither does it have the same signature as
|
||||
// > in the derived class, so there is no reason for it to apply.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26456)
|
||||
KeyEvent& operator=(const KeyEvent&) & = default;
|
||||
KeyEvent& operator=(KeyEvent&&) & = default;
|
||||
#pragma warning(pop)
|
||||
|
||||
INPUT_RECORD ToInputRecord() const noexcept override;
|
||||
InputEventType EventType() const noexcept override;
|
||||
|
||||
constexpr bool IsShiftPressed() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(GetActiveModifierKeys(), SHIFT_PRESSED);
|
||||
}
|
||||
|
||||
constexpr bool IsAltPressed() const noexcept
|
||||
{
|
||||
return WI_IsAnyFlagSet(GetActiveModifierKeys(), ALT_PRESSED);
|
||||
}
|
||||
|
||||
constexpr bool IsCtrlPressed() const noexcept
|
||||
{
|
||||
return WI_IsAnyFlagSet(GetActiveModifierKeys(), CTRL_PRESSED);
|
||||
}
|
||||
|
||||
constexpr bool IsAltGrPressed() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(GetActiveModifierKeys(), LEFT_CTRL_PRESSED) &&
|
||||
WI_IsFlagSet(GetActiveModifierKeys(), RIGHT_ALT_PRESSED);
|
||||
}
|
||||
|
||||
constexpr bool IsModifierPressed() const noexcept
|
||||
{
|
||||
return WI_IsAnyFlagSet(GetActiveModifierKeys(), MOD_PRESSED);
|
||||
}
|
||||
|
||||
constexpr bool IsCursorKey() const noexcept
|
||||
{
|
||||
// true iff vk in [End, Home, Left, Up, Right, Down]
|
||||
return (_virtualKeyCode >= VK_END) && (_virtualKeyCode <= VK_DOWN);
|
||||
}
|
||||
|
||||
constexpr bool IsAltNumpadSet() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(GetActiveModifierKeys(), ALTNUMPAD_BIT);
|
||||
}
|
||||
|
||||
constexpr bool IsKeyDown() const noexcept
|
||||
{
|
||||
return _keyDown;
|
||||
}
|
||||
|
||||
constexpr bool IsPauseKey() const noexcept
|
||||
{
|
||||
return (_virtualKeyCode == VK_PAUSE);
|
||||
}
|
||||
|
||||
constexpr WORD GetRepeatCount() const noexcept
|
||||
{
|
||||
return _repeatCount;
|
||||
}
|
||||
|
||||
constexpr WORD GetVirtualKeyCode() const noexcept
|
||||
{
|
||||
return _virtualKeyCode;
|
||||
}
|
||||
|
||||
constexpr WORD GetVirtualScanCode() const noexcept
|
||||
{
|
||||
return _virtualScanCode;
|
||||
}
|
||||
|
||||
constexpr wchar_t GetCharData() const noexcept
|
||||
{
|
||||
return _charData;
|
||||
}
|
||||
|
||||
constexpr DWORD GetActiveModifierKeys() const noexcept
|
||||
{
|
||||
return static_cast<DWORD>(_activeModifierKeys);
|
||||
}
|
||||
|
||||
void SetKeyDown(const bool keyDown) noexcept;
|
||||
void SetRepeatCount(const WORD repeatCount) noexcept;
|
||||
void SetVirtualKeyCode(const WORD virtualKeyCode) noexcept;
|
||||
void SetVirtualScanCode(const WORD virtualScanCode) noexcept;
|
||||
void SetCharData(const char character) noexcept;
|
||||
void SetCharData(const wchar_t character) noexcept;
|
||||
|
||||
void SetActiveModifierKeys(const DWORD activeModifierKeys) noexcept;
|
||||
void DeactivateModifierKey(const ModifierKeyState modifierKey) noexcept;
|
||||
void ActivateModifierKey(const ModifierKeyState modifierKey) noexcept;
|
||||
bool DoActiveModifierKeysMatch(const std::unordered_set<ModifierKeyState>& consoleModifiers) const noexcept;
|
||||
bool IsCommandLineEditingKey() const noexcept;
|
||||
bool IsPopupKey() const noexcept;
|
||||
|
||||
// Function Description:
|
||||
// - Returns true if the given VKey represents a modifier key - shift, alt,
|
||||
// control or the Win key.
|
||||
// Arguments:
|
||||
// - vkey: the VKEY to check
|
||||
// Return Value:
|
||||
// - true iff the key is a modifier key.
|
||||
constexpr static bool IsModifierKey(const WORD vkey)
|
||||
{
|
||||
return (vkey == VK_CONTROL) ||
|
||||
(vkey == VK_LCONTROL) ||
|
||||
(vkey == VK_RCONTROL) ||
|
||||
(vkey == VK_MENU) ||
|
||||
(vkey == VK_LMENU) ||
|
||||
(vkey == VK_RMENU) ||
|
||||
(vkey == VK_SHIFT) ||
|
||||
(vkey == VK_LSHIFT) ||
|
||||
(vkey == VK_RSHIFT) ||
|
||||
// There is no VK_WIN
|
||||
(vkey == VK_LWIN) ||
|
||||
(vkey == VK_RWIN);
|
||||
};
|
||||
|
||||
private:
|
||||
bool _keyDown;
|
||||
WORD _repeatCount;
|
||||
WORD _virtualKeyCode;
|
||||
WORD _virtualScanCode;
|
||||
wchar_t _charData;
|
||||
Modifiers _activeModifierKeys;
|
||||
|
||||
friend constexpr bool operator==(const KeyEvent& a, const KeyEvent& b) noexcept;
|
||||
#ifdef UNIT_TESTING
|
||||
friend std::wostream& operator<<(std::wostream& stream, const KeyEvent* const pKeyEvent);
|
||||
#endif
|
||||
};
|
||||
|
||||
constexpr bool operator==(const KeyEvent& a, const KeyEvent& b) noexcept
|
||||
{
|
||||
return (a._keyDown == b._keyDown &&
|
||||
a._repeatCount == b._repeatCount &&
|
||||
a._virtualKeyCode == b._virtualKeyCode &&
|
||||
a._virtualScanCode == b._virtualScanCode &&
|
||||
a._charData == b._charData &&
|
||||
a._activeModifierKeys == b._activeModifierKeys);
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
std::wostream& operator<<(std::wostream& stream, const KeyEvent* const pKeyEvent);
|
||||
#endif
|
||||
|
||||
class MouseEvent : public IInputEvent
|
||||
constexpr INPUT_RECORD SynthesizeMouseEvent(til::point dwMousePosition, uint32_t dwButtonState, uint32_t dwControlKeyState, uint32_t dwEventFlags)
|
||||
{
|
||||
public:
|
||||
constexpr MouseEvent(const MOUSE_EVENT_RECORD& record) :
|
||||
_position{ til::wrap_coord(record.dwMousePosition) },
|
||||
_buttonState{ record.dwButtonState },
|
||||
_activeModifierKeys{ record.dwControlKeyState },
|
||||
_eventFlags{ record.dwEventFlags }
|
||||
{
|
||||
}
|
||||
return INPUT_RECORD{
|
||||
.EventType = MOUSE_EVENT,
|
||||
.Event = {
|
||||
.MouseEvent = {
|
||||
.dwMousePosition = {
|
||||
::base::saturated_cast<SHORT>(dwMousePosition.x),
|
||||
::base::saturated_cast<SHORT>(dwMousePosition.y),
|
||||
},
|
||||
.dwButtonState = dwButtonState,
|
||||
.dwControlKeyState = dwControlKeyState,
|
||||
.dwEventFlags = dwEventFlags,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constexpr MouseEvent(const til::point position,
|
||||
const DWORD buttonState,
|
||||
const DWORD activeModifierKeys,
|
||||
const DWORD eventFlags) :
|
||||
_position{ position },
|
||||
_buttonState{ buttonState },
|
||||
_activeModifierKeys{ activeModifierKeys },
|
||||
_eventFlags{ eventFlags }
|
||||
{
|
||||
}
|
||||
|
||||
~MouseEvent();
|
||||
MouseEvent(const MouseEvent&) = default;
|
||||
MouseEvent(MouseEvent&&) = default;
|
||||
MouseEvent& operator=(const MouseEvent&) & = default;
|
||||
MouseEvent& operator=(MouseEvent&&) & = default;
|
||||
|
||||
INPUT_RECORD ToInputRecord() const noexcept override;
|
||||
InputEventType EventType() const noexcept override;
|
||||
|
||||
constexpr bool IsMouseMoveEvent() const noexcept
|
||||
{
|
||||
return _eventFlags == MOUSE_MOVED;
|
||||
}
|
||||
|
||||
constexpr til::point GetPosition() const noexcept
|
||||
{
|
||||
return _position;
|
||||
}
|
||||
|
||||
constexpr DWORD GetButtonState() const noexcept
|
||||
{
|
||||
return _buttonState;
|
||||
}
|
||||
|
||||
constexpr DWORD GetActiveModifierKeys() const noexcept
|
||||
{
|
||||
return _activeModifierKeys;
|
||||
}
|
||||
|
||||
constexpr DWORD GetEventFlags() const noexcept
|
||||
{
|
||||
return _eventFlags;
|
||||
}
|
||||
|
||||
void SetPosition(const til::point position) noexcept;
|
||||
void SetButtonState(const DWORD buttonState) noexcept;
|
||||
void SetActiveModifierKeys(const DWORD activeModifierKeys) noexcept;
|
||||
void SetEventFlags(const DWORD eventFlags) noexcept;
|
||||
|
||||
private:
|
||||
til::point _position;
|
||||
DWORD _buttonState;
|
||||
DWORD _activeModifierKeys;
|
||||
DWORD _eventFlags;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend std::wostream& operator<<(std::wostream& stream, const MouseEvent* const pMouseEvent);
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
std::wostream& operator<<(std::wostream& stream, const MouseEvent* const pMouseEvent);
|
||||
#endif
|
||||
|
||||
class WindowBufferSizeEvent : public IInputEvent
|
||||
constexpr INPUT_RECORD SynthesizeWindowBufferSizeEvent(til::size dwSize)
|
||||
{
|
||||
public:
|
||||
constexpr WindowBufferSizeEvent(const WINDOW_BUFFER_SIZE_RECORD& record) :
|
||||
_size{ til::wrap_coord_size(record.dwSize) }
|
||||
{
|
||||
}
|
||||
return INPUT_RECORD{
|
||||
.EventType = WINDOW_BUFFER_SIZE_EVENT,
|
||||
.Event = {
|
||||
.WindowBufferSizeEvent = {
|
||||
.dwSize = {
|
||||
::base::saturated_cast<SHORT>(dwSize.width),
|
||||
::base::saturated_cast<SHORT>(dwSize.height),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constexpr WindowBufferSizeEvent(const til::size size) :
|
||||
_size{ size }
|
||||
{
|
||||
}
|
||||
|
||||
~WindowBufferSizeEvent();
|
||||
WindowBufferSizeEvent(const WindowBufferSizeEvent&) = default;
|
||||
WindowBufferSizeEvent(WindowBufferSizeEvent&&) = default;
|
||||
WindowBufferSizeEvent& operator=(const WindowBufferSizeEvent&) & = default;
|
||||
WindowBufferSizeEvent& operator=(WindowBufferSizeEvent&&) & = default;
|
||||
|
||||
INPUT_RECORD ToInputRecord() const noexcept override;
|
||||
InputEventType EventType() const noexcept override;
|
||||
|
||||
constexpr til::size GetSize() const noexcept
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
void SetSize(const til::size size) noexcept;
|
||||
|
||||
private:
|
||||
til::size _size;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend std::wostream& operator<<(std::wostream& stream, const WindowBufferSizeEvent* const pEvent);
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
std::wostream& operator<<(std::wostream& stream, const WindowBufferSizeEvent* const pEvent);
|
||||
#endif
|
||||
|
||||
class MenuEvent : public IInputEvent
|
||||
constexpr INPUT_RECORD SynthesizeMenuEvent(uint32_t dwCommandId)
|
||||
{
|
||||
public:
|
||||
constexpr MenuEvent(const MENU_EVENT_RECORD& record) :
|
||||
_commandId{ record.dwCommandId }
|
||||
{
|
||||
}
|
||||
return INPUT_RECORD{
|
||||
.EventType = MENU_EVENT,
|
||||
.Event = {
|
||||
.MenuEvent = {
|
||||
.dwCommandId = dwCommandId,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constexpr MenuEvent(const UINT commandId) :
|
||||
_commandId{ commandId }
|
||||
{
|
||||
}
|
||||
|
||||
~MenuEvent();
|
||||
MenuEvent(const MenuEvent&) = default;
|
||||
MenuEvent(MenuEvent&&) = default;
|
||||
MenuEvent& operator=(const MenuEvent&) & = default;
|
||||
MenuEvent& operator=(MenuEvent&&) & = default;
|
||||
|
||||
INPUT_RECORD ToInputRecord() const noexcept override;
|
||||
InputEventType EventType() const noexcept override;
|
||||
|
||||
constexpr UINT GetCommandId() const noexcept
|
||||
{
|
||||
return _commandId;
|
||||
}
|
||||
|
||||
void SetCommandId(const UINT commandId) noexcept;
|
||||
|
||||
private:
|
||||
UINT _commandId;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend std::wostream& operator<<(std::wostream& stream, const MenuEvent* const pMenuEvent);
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
std::wostream& operator<<(std::wostream& stream, const MenuEvent* const pMenuEvent);
|
||||
#endif
|
||||
|
||||
class FocusEvent : public IInputEvent
|
||||
constexpr INPUT_RECORD SynthesizeFocusEvent(bool bSetFocus)
|
||||
{
|
||||
public:
|
||||
constexpr FocusEvent(const FOCUS_EVENT_RECORD& record) :
|
||||
_focus{ !!record.bSetFocus }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr FocusEvent(const bool focus) :
|
||||
_focus{ focus }
|
||||
{
|
||||
}
|
||||
|
||||
~FocusEvent();
|
||||
FocusEvent(const FocusEvent&) = default;
|
||||
FocusEvent(FocusEvent&&) = default;
|
||||
FocusEvent& operator=(const FocusEvent&) & = default;
|
||||
FocusEvent& operator=(FocusEvent&&) & = default;
|
||||
|
||||
INPUT_RECORD ToInputRecord() const noexcept override;
|
||||
InputEventType EventType() const noexcept override;
|
||||
|
||||
constexpr bool GetFocus() const noexcept
|
||||
{
|
||||
return _focus;
|
||||
}
|
||||
|
||||
void SetFocus(const bool focus) noexcept;
|
||||
|
||||
private:
|
||||
bool _focus;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend std::wostream& operator<<(std::wostream& stream, const FocusEvent* const pFocusEvent);
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
std::wostream& operator<<(std::wostream& stream, const FocusEvent* const pFocusEvent);
|
||||
#endif
|
||||
return INPUT_RECORD{
|
||||
.EventType = FOCUS_EVENT,
|
||||
.Event = {
|
||||
.FocusEvent = {
|
||||
.bSetFocus = bSetFocus,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -16,12 +16,6 @@
|
||||
<ClCompile Include="..\convert.cpp" />
|
||||
<ClCompile Include="..\colorTable.cpp" />
|
||||
<ClCompile Include="..\GlyphWidth.cpp" />
|
||||
<ClCompile Include="..\MouseEvent.cpp" />
|
||||
<ClCompile Include="..\FocusEvent.cpp" />
|
||||
<ClCompile Include="..\IInputEvent.cpp" />
|
||||
<ClCompile Include="..\KeyEvent.cpp" />
|
||||
<ClCompile Include="..\MenuEvent.cpp" />
|
||||
<ClCompile Include="..\ModifierKeyState.cpp" />
|
||||
<ClCompile Include="..\ScreenInfoUiaProviderBase.cpp" />
|
||||
<ClCompile Include="..\sgrStack.cpp" />
|
||||
<ClCompile Include="..\ThemeUtils.cpp" />
|
||||
@ -30,7 +24,6 @@
|
||||
<ClCompile Include="..\TermControlUiaTextRange.cpp" />
|
||||
<ClCompile Include="..\TermControlUiaProvider.cpp" />
|
||||
<ClCompile Include="..\Viewport.cpp" />
|
||||
<ClCompile Include="..\WindowBufferSizeEvent.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@ -66,4 +59,4 @@
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
@ -24,30 +24,9 @@
|
||||
<ClCompile Include="..\colorTable.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\MouseEvent.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\FocusEvent.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\IInputEvent.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\KeyEvent.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\MenuEvent.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\ModifierKeyState.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Viewport.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\WindowBufferSizeEvent.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -69,9 +48,6 @@
|
||||
<ClCompile Include="..\ThemeUtils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Environment.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\sgrStack.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -116,24 +92,6 @@
|
||||
<ClInclude Include="..\UiaTextRangeBase.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\inc\CodepointWidthDetector.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\inc\convert.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\inc\GlyphWidth.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\inc\IInputEvent.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\inc\Viewport.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\precomp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ScreenInfoUiaProviderBase.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -152,9 +110,6 @@
|
||||
<ClInclude Include="..\inc\ThemeUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\inc\Environment.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\inc\sgrStack.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@ -30,15 +30,9 @@ PRECOMPILED_INCLUDE = ..\precomp.h
|
||||
SOURCES= \
|
||||
..\CodepointWidthDetector.cpp \
|
||||
..\ColorFix.cpp \
|
||||
..\IInputEvent.cpp \
|
||||
..\FocusEvent.cpp \
|
||||
..\GlyphWidth.cpp \
|
||||
..\KeyEvent.cpp \
|
||||
..\MenuEvent.cpp \
|
||||
..\ModifierKeyState.cpp \
|
||||
..\MouseEvent.cpp \
|
||||
..\Viewport.cpp \
|
||||
..\WindowBufferSizeEvent.cpp \
|
||||
..\convert.cpp \
|
||||
..\colorTable.cpp \
|
||||
..\utils.cpp \
|
||||
|
||||
@ -45,9 +45,28 @@
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="KeyEvent">
|
||||
<DisplayString Condition="_keyDown">{{↓ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}}</DisplayString>
|
||||
<DisplayString Condition="!_keyDown">{{↑ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}}</DisplayString>
|
||||
<Type Name="_KEY_EVENT_RECORD">
|
||||
<DisplayString Condition="bKeyDown != 0">↓ rep:{wRepeatCount} vk:{wVirtualKeyCode} sc:{wVirtualScanCode} ch:{uChar.UnicodeChar} ctrl:{(unsigned short)dwControlKeyState,x}</DisplayString>
|
||||
<DisplayString Condition="bKeyDown == 0">↑ rep:{wRepeatCount} vk:{wVirtualKeyCode} sc:{wVirtualScanCode} ch:{uChar.UnicodeChar} ctrl:{(unsigned short)dwControlKeyState,x}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="_MOUSE_EVENT_RECORD">
|
||||
<DisplayString>pos:{dwMousePosition.X},{dwMousePosition.Y} state:{dwButtonState,x} ctrl:{(unsigned short)dwControlKeyState,x} flags:{(unsigned short)dwEventFlags,x}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="_WINDOW_BUFFER_SIZE_RECORD">
|
||||
<DisplayString>size:{dwSize.X},{dwSize.Y}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="_MENU_EVENT_RECORD">
|
||||
<DisplayString>id:{dwCommandId}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="_FOCUS_EVENT_RECORD">
|
||||
<DisplayString>focus:{bSetFocus}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="_INPUT_RECORD">
|
||||
<DisplayString Condition="EventType == 0x0001">Key {Event.KeyEvent}</DisplayString>
|
||||
<DisplayString Condition="EventType == 0x0002">Mouse {Event.MouseEvent}</DisplayString>
|
||||
<DisplayString Condition="EventType == 0x0004">WindowBufferSize {Event.WindowBufferSizeEvent}</DisplayString>
|
||||
<DisplayString Condition="EventType == 0x0008">Menu {Event.MenuEvent}</DisplayString>
|
||||
<DisplayString Condition="EventType == 0x0010">Focus {Event.FocusEvent}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="til::size">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user