719 lines
28 KiB
C++
719 lines
28 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
|
|
#include <thread>
|
|
|
|
#include "..\..\interactivity\onecore\SystemConfigurationProvider.hpp"
|
|
|
|
// some assumptions have been made on this value. only change it if you have a good reason to.
|
|
#define NUMBER_OF_SCENARIO_INPUTS 10
|
|
#define READ_BATCH 3
|
|
|
|
using WEX::Logging::Log;
|
|
using namespace WEX::Common;
|
|
|
|
// This class is intended to test:
|
|
// FlushConsoleInputBuffer
|
|
// PeekConsoleInput
|
|
// ReadConsoleInput
|
|
// WriteConsoleInput
|
|
// GetNumberOfConsoleInputEvents
|
|
// GetNumberOfConsoleMouseButtons
|
|
class InputTests
|
|
{
|
|
BEGIN_TEST_CLASS(InputTests)
|
|
END_TEST_CLASS()
|
|
|
|
TEST_CLASS_SETUP(TestSetup);
|
|
TEST_CLASS_CLEANUP(TestCleanup);
|
|
|
|
// note: GetNumberOfConsoleMouseButtons crashes with nullptr, so there's no negative test
|
|
TEST_METHOD(TestGetMouseButtonsValid);
|
|
|
|
TEST_METHOD(TestInputScenario);
|
|
TEST_METHOD(TestFlushValid);
|
|
TEST_METHOD(TestFlushInvalid);
|
|
TEST_METHOD(TestPeekConsoleInvalid);
|
|
TEST_METHOD(TestReadConsoleInvalid);
|
|
TEST_METHOD(TestWriteConsoleInvalid);
|
|
|
|
TEST_METHOD(TestReadWaitOnHandle);
|
|
|
|
TEST_METHOD(TestReadConsolePasswordScenario);
|
|
|
|
TEST_METHOD(TestMouseWheelReadConsoleMouseInput);
|
|
TEST_METHOD(TestMouseHorizWheelReadConsoleMouseInput);
|
|
TEST_METHOD(TestMouseWheelReadConsoleNoMouseInput);
|
|
TEST_METHOD(TestMouseHorizWheelReadConsoleNoMouseInput);
|
|
TEST_METHOD(TestMouseWheelReadConsoleInputQuickEdit);
|
|
TEST_METHOD(TestMouseHorizWheelReadConsoleInputQuickEdit);
|
|
TEST_METHOD(RawReadUnpacksCoalescedInputRecords);
|
|
|
|
BEGIN_TEST_METHOD(TestVtInputGeneration)
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD();
|
|
};
|
|
|
|
void VerifyNumberOfInputRecords(const HANDLE hConsoleInput, _In_ DWORD nInputs)
|
|
{
|
|
WEX::TestExecution::SetVerifyOutput verifySettings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures);
|
|
DWORD nInputEvents = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(hConsoleInput, &nInputEvents));
|
|
VERIFY_ARE_EQUAL(nInputEvents,
|
|
nInputs,
|
|
L"Verify number of input events");
|
|
}
|
|
|
|
bool InputTests::TestSetup()
|
|
{
|
|
const bool fRet = Common::TestBufferSetup();
|
|
|
|
HANDLE hConsoleInput = GetStdInputHandle();
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(hConsoleInput));
|
|
VerifyNumberOfInputRecords(hConsoleInput, 0);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
bool InputTests::TestCleanup()
|
|
{
|
|
return Common::TestBufferCleanup();
|
|
}
|
|
|
|
void InputTests::TestGetMouseButtonsValid()
|
|
{
|
|
DWORD nMouseButtons = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(OneCoreDelay::GetNumberOfConsoleMouseButtons(&nMouseButtons));
|
|
|
|
DWORD dwButtonsExpected = (DWORD)-1;
|
|
if (OneCoreDelay::IsGetSystemMetricsPresent())
|
|
{
|
|
dwButtonsExpected = (DWORD)GetSystemMetrics(SM_CMOUSEBUTTONS);
|
|
}
|
|
else
|
|
{
|
|
dwButtonsExpected = Microsoft::Console::Interactivity::OneCore::SystemConfigurationProvider::s_DefaultNumberOfMouseButtons;
|
|
}
|
|
|
|
VERIFY_ARE_EQUAL(dwButtonsExpected, nMouseButtons);
|
|
}
|
|
|
|
void GenerateAndWriteInputRecords(const HANDLE hConsoleInput,
|
|
const UINT cRecordsToGenerate,
|
|
_Out_writes_(cRecs) INPUT_RECORD* prgRecs,
|
|
const DWORD cRecs,
|
|
_Out_ PDWORD pdwWritten)
|
|
{
|
|
Log::Comment(String().Format(L"Generating %d input events", cRecordsToGenerate));
|
|
for (UINT iRecord = 0; iRecord < cRecs; iRecord++)
|
|
{
|
|
prgRecs[iRecord].EventType = KEY_EVENT;
|
|
prgRecs[iRecord].Event.KeyEvent.bKeyDown = FALSE;
|
|
prgRecs[iRecord].Event.KeyEvent.wRepeatCount = 1;
|
|
prgRecs[iRecord].Event.KeyEvent.wVirtualKeyCode = ('A' + (WORD)iRecord);
|
|
}
|
|
|
|
Log::Comment(L"Writing events");
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInput(hConsoleInput, prgRecs, cRecs, pdwWritten));
|
|
VERIFY_ARE_EQUAL(*pdwWritten,
|
|
cRecs,
|
|
L"verify number written");
|
|
}
|
|
|
|
void InputTests::TestInputScenario()
|
|
{
|
|
Log::Comment(L"Get input handle");
|
|
HANDLE hConsoleInput = GetStdInputHandle();
|
|
|
|
DWORD nWrittenEvents = (DWORD)-1;
|
|
INPUT_RECORD rgInputRecords[NUMBER_OF_SCENARIO_INPUTS] = { 0 };
|
|
GenerateAndWriteInputRecords(hConsoleInput, NUMBER_OF_SCENARIO_INPUTS, rgInputRecords, ARRAYSIZE(rgInputRecords), &nWrittenEvents);
|
|
|
|
VerifyNumberOfInputRecords(hConsoleInput, ARRAYSIZE(rgInputRecords));
|
|
|
|
Log::Comment(L"Peeking events");
|
|
INPUT_RECORD rgPeekedRecords[NUMBER_OF_SCENARIO_INPUTS] = { 0 };
|
|
DWORD nPeekedEvents = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(PeekConsoleInput(hConsoleInput, rgPeekedRecords, ARRAYSIZE(rgPeekedRecords), &nPeekedEvents));
|
|
VERIFY_ARE_EQUAL(nPeekedEvents, nWrittenEvents, L"We should be able to peek at all of the records we've written");
|
|
for (UINT iPeekedRecord = 0; iPeekedRecord < nPeekedEvents; iPeekedRecord++)
|
|
{
|
|
VERIFY_ARE_EQUAL(rgPeekedRecords[iPeekedRecord],
|
|
rgInputRecords[iPeekedRecord],
|
|
L"make sure our peeked records match what we input");
|
|
}
|
|
|
|
// read inputs 3 at a time until we've read them all. since the number we're batching by doesn't match the number of
|
|
// total events, we need to account for the last incomplete read we'll perform.
|
|
const UINT cIterations = (NUMBER_OF_SCENARIO_INPUTS / READ_BATCH) + ((NUMBER_OF_SCENARIO_INPUTS % READ_BATCH > 0) ? 1 : 0);
|
|
for (UINT iIteration = 0; iIteration < cIterations; iIteration++)
|
|
{
|
|
const bool fIsLastIteration = (iIteration + 1) > (NUMBER_OF_SCENARIO_INPUTS / READ_BATCH);
|
|
Log::Comment(String().Format(L"Reading inputs (iteration %d/%d)%s",
|
|
iIteration + 1,
|
|
cIterations,
|
|
fIsLastIteration ? L" (last one)" : L""));
|
|
|
|
INPUT_RECORD rgReadRecords[READ_BATCH] = { 0 };
|
|
DWORD nReadEvents = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInput(hConsoleInput, rgReadRecords, ARRAYSIZE(rgReadRecords), &nReadEvents));
|
|
|
|
DWORD dwExpectedEventsRead = READ_BATCH;
|
|
if (fIsLastIteration)
|
|
{
|
|
// on the last iteration, we'll have an incomplete read. account for it here.
|
|
dwExpectedEventsRead = NUMBER_OF_SCENARIO_INPUTS % READ_BATCH;
|
|
}
|
|
|
|
VERIFY_ARE_EQUAL(nReadEvents, dwExpectedEventsRead);
|
|
for (UINT iReadRecord = 0; iReadRecord < nReadEvents; iReadRecord++)
|
|
{
|
|
const UINT iInputRecord = iReadRecord + (iIteration * READ_BATCH);
|
|
VERIFY_ARE_EQUAL(rgReadRecords[iReadRecord],
|
|
rgInputRecords[iInputRecord],
|
|
String().Format(L"verify record %d", iInputRecord));
|
|
}
|
|
|
|
DWORD nInputEventsAfterRead = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(hConsoleInput, &nInputEventsAfterRead));
|
|
|
|
DWORD dwExpectedEventsAfterRead = (NUMBER_OF_SCENARIO_INPUTS - (READ_BATCH * (iIteration + 1)));
|
|
if (fIsLastIteration)
|
|
{
|
|
dwExpectedEventsAfterRead = 0;
|
|
}
|
|
VERIFY_ARE_EQUAL(dwExpectedEventsAfterRead,
|
|
nInputEventsAfterRead,
|
|
L"verify number of remaining inputs");
|
|
}
|
|
}
|
|
|
|
void InputTests::TestFlushValid()
|
|
{
|
|
Log::Comment(L"Get input handle");
|
|
HANDLE hConsoleInput = GetStdInputHandle();
|
|
|
|
DWORD nWrittenEvents = (DWORD)-1;
|
|
INPUT_RECORD rgInputRecords[NUMBER_OF_SCENARIO_INPUTS] = { 0 };
|
|
GenerateAndWriteInputRecords(hConsoleInput, NUMBER_OF_SCENARIO_INPUTS, rgInputRecords, ARRAYSIZE(rgInputRecords), &nWrittenEvents);
|
|
|
|
VerifyNumberOfInputRecords(hConsoleInput, ARRAYSIZE(rgInputRecords));
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(hConsoleInput));
|
|
|
|
VerifyNumberOfInputRecords(hConsoleInput, 0);
|
|
}
|
|
|
|
void InputTests::TestFlushInvalid()
|
|
{
|
|
// NOTE: FlushConsoleInputBuffer(nullptr) crashes, so we don't verify that here.
|
|
VERIFY_WIN32_BOOL_FAILED(FlushConsoleInputBuffer(INVALID_HANDLE_VALUE));
|
|
}
|
|
|
|
void InputTests::TestPeekConsoleInvalid()
|
|
{
|
|
DWORD nPeeked = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_FAILED(PeekConsoleInput(INVALID_HANDLE_VALUE, nullptr, 0, &nPeeked)); // NOTE: nPeeked is required
|
|
VERIFY_ARE_EQUAL(nPeeked, (DWORD)0);
|
|
|
|
HANDLE hConsoleInput = GetStdInputHandle();
|
|
|
|
nPeeked = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_FAILED(PeekConsoleInput(hConsoleInput, nullptr, 5, &nPeeked));
|
|
VERIFY_ARE_EQUAL(nPeeked, (DWORD)0);
|
|
|
|
DWORD nWritten = (DWORD)-1;
|
|
INPUT_RECORD ir = { 0 };
|
|
GenerateAndWriteInputRecords(hConsoleInput, 1, &ir, 1, &nWritten);
|
|
|
|
VerifyNumberOfInputRecords(hConsoleInput, 1);
|
|
|
|
nPeeked = (DWORD)-1;
|
|
INPUT_RECORD irPeeked = { 0 };
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(PeekConsoleInput(hConsoleInput, &irPeeked, 0, &nPeeked));
|
|
VERIFY_ARE_EQUAL(nPeeked, (DWORD)0, L"Verify that an empty array doesn't cause peeks to get written");
|
|
|
|
VerifyNumberOfInputRecords(hConsoleInput, 1);
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(hConsoleInput));
|
|
}
|
|
|
|
void InputTests::TestReadConsoleInvalid()
|
|
{
|
|
DWORD nRead = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_FAILED(ReadConsoleInput(0, nullptr, 0, &nRead));
|
|
VERIFY_ARE_EQUAL(nRead, (DWORD)0);
|
|
|
|
nRead = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_FAILED(ReadConsoleInput(INVALID_HANDLE_VALUE, nullptr, 0, &nRead));
|
|
VERIFY_ARE_EQUAL(nRead, (DWORD)0);
|
|
|
|
// NOTE: ReadConsoleInput blocks until at least one input event is read, even if the operation would result in no
|
|
// records actually being read (e.g. valid handle, NULL lpBuffer)
|
|
|
|
HANDLE hConsoleInput = GetStdInputHandle();
|
|
|
|
DWORD nWritten = (DWORD)-1;
|
|
INPUT_RECORD irWrite = { 0 };
|
|
GenerateAndWriteInputRecords(hConsoleInput, 1, &irWrite, 1, &nWritten);
|
|
VerifyNumberOfInputRecords(hConsoleInput, 1);
|
|
|
|
nRead = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInput(hConsoleInput, nullptr, 0, &nRead));
|
|
VERIFY_ARE_EQUAL(nRead, (DWORD)0);
|
|
|
|
INPUT_RECORD irRead = { 0 };
|
|
nRead = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInput(hConsoleInput, &irRead, 0, &nRead));
|
|
VERIFY_ARE_EQUAL(nRead, (DWORD)0);
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(hConsoleInput));
|
|
}
|
|
|
|
void InputTests::TestWriteConsoleInvalid()
|
|
{
|
|
DWORD nWrite = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_FAILED(WriteConsoleInput(0, nullptr, 0, &nWrite));
|
|
VERIFY_ARE_EQUAL(nWrite, (DWORD)0);
|
|
|
|
// weird: WriteConsoleInput with INVALID_HANDLE_VALUE writes garbage to lpNumberOfEventsWritten, whereas
|
|
// [Read|Peek]ConsoleInput don't. This is a legacy behavior that we don't want to change.
|
|
nWrite = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_FAILED(WriteConsoleInput(INVALID_HANDLE_VALUE, nullptr, 0, &nWrite));
|
|
|
|
HANDLE hConsoleInput = GetStdInputHandle();
|
|
|
|
nWrite = (DWORD)-1;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInput(hConsoleInput, nullptr, 0, &nWrite));
|
|
VERIFY_ARE_EQUAL(nWrite, (DWORD)0);
|
|
|
|
nWrite = (DWORD)-1;
|
|
INPUT_RECORD irWrite = { 0 };
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInput(hConsoleInput, &irWrite, 0, &nWrite));
|
|
VERIFY_ARE_EQUAL(nWrite, (DWORD)0);
|
|
}
|
|
|
|
void FillInputRecordHelper(_Inout_ INPUT_RECORD* const pir, _In_ wchar_t wch, _In_ bool fIsKeyDown)
|
|
{
|
|
pir->EventType = KEY_EVENT;
|
|
pir->Event.KeyEvent.wRepeatCount = 1;
|
|
pir->Event.KeyEvent.dwControlKeyState = 0;
|
|
pir->Event.KeyEvent.bKeyDown = !!fIsKeyDown;
|
|
pir->Event.KeyEvent.uChar.UnicodeChar = wch;
|
|
|
|
// This only holds true for capital letters from A-Z.
|
|
VERIFY_IS_TRUE(wch >= 'A' && wch <= 'Z');
|
|
pir->Event.KeyEvent.wVirtualKeyCode = wch;
|
|
|
|
pir->Event.KeyEvent.wVirtualScanCode = static_cast<WORD>(MapVirtualKeyW(pir->Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC));
|
|
}
|
|
|
|
void InputTests::TestReadConsolePasswordScenario()
|
|
{
|
|
if (!OneCoreDelay::IsPostMessageWPresent())
|
|
{
|
|
Log::Comment(L"Password scenario can't be checked on platform without window message queuing.");
|
|
Log::Result(WEX::Logging::TestResults::Skipped);
|
|
return;
|
|
}
|
|
|
|
// Scenario inspired by net use's password capture code.
|
|
HANDLE const hIn = GetStdHandle(STD_INPUT_HANDLE);
|
|
|
|
// 1. Set up our mode to be raw input (mimicking method used by "net use")
|
|
DWORD mode = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT;
|
|
GetConsoleMode(hIn, &mode);
|
|
|
|
SetConsoleMode(hIn, (~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT)) & mode);
|
|
|
|
// 2. Flush and write some text into the input buffer (added for sake of test.)
|
|
PCWSTR pwszExpected = L"QUE";
|
|
DWORD const cBuffer = static_cast<DWORD>(wcslen(pwszExpected) * 2);
|
|
wistd::unique_ptr<INPUT_RECORD[]> irBuffer = wil::make_unique_nothrow<INPUT_RECORD[]>(cBuffer);
|
|
FillInputRecordHelper(&irBuffer.get()[0], pwszExpected[0], true);
|
|
FillInputRecordHelper(&irBuffer.get()[1], pwszExpected[0], false);
|
|
FillInputRecordHelper(&irBuffer.get()[2], pwszExpected[1], true);
|
|
FillInputRecordHelper(&irBuffer.get()[3], pwszExpected[1], false);
|
|
FillInputRecordHelper(&irBuffer.get()[4], pwszExpected[2], true);
|
|
FillInputRecordHelper(&irBuffer.get()[5], pwszExpected[2], false);
|
|
|
|
DWORD dwWritten;
|
|
FlushConsoleInputBuffer(hIn);
|
|
WriteConsoleInputW(hIn, irBuffer.get(), cBuffer, &dwWritten);
|
|
|
|
// Press "enter" key on the window to signify the user pressing enter at the end of the password.
|
|
|
|
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(PostMessageW(GetConsoleWindow(), WM_KEYDOWN, VK_RETURN, 0));
|
|
|
|
// 3. Set up our read loop (mimicking password capture methodology from "net use" command.)
|
|
size_t const buflen = (cBuffer / 2) + 1; // key down and key up will be coalesced into one.
|
|
wistd::unique_ptr<wchar_t[]> buf = wil::make_unique_nothrow<wchar_t[]>(buflen);
|
|
size_t len = 0;
|
|
VERIFY_IS_NOT_NULL(buf);
|
|
wchar_t* bufPtr = buf.get();
|
|
|
|
while (true)
|
|
{
|
|
wchar_t ch;
|
|
DWORD c;
|
|
int err = ReadConsoleW(hIn, &ch, 1, &c, 0);
|
|
|
|
if (!err || c != 1)
|
|
{
|
|
ch = 0xffff; // end of line
|
|
}
|
|
|
|
if ((ch == 0xD) || (ch == 0xffff)) // CR or end of line
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (ch == 0x8) // backspace
|
|
{
|
|
if (bufPtr != buf.get())
|
|
{
|
|
bufPtr--;
|
|
len--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*bufPtr = ch;
|
|
if (len < buflen)
|
|
{
|
|
bufPtr++;
|
|
}
|
|
len++;
|
|
}
|
|
}
|
|
|
|
// 4. Restore console mode and terminate string (mimics "net use" behavior)
|
|
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), mode);
|
|
|
|
*bufPtr = L'\0'; // null terminate string
|
|
putchar('\n');
|
|
|
|
// 5. Verify our string got read back (added for sake of the test)
|
|
VERIFY_ARE_EQUAL(String(pwszExpected), String(buf.get()));
|
|
VERIFY_ARE_EQUAL(wcslen(pwszExpected), len);
|
|
}
|
|
|
|
void TestMouseWheelReadConsoleInputHelper(const UINT /*msg*/, const DWORD /*dwEventFlagsExpected*/, const DWORD /*dwConsoleMode*/)
|
|
{
|
|
if (!OneCoreDelay::IsIsWindowPresent())
|
|
{
|
|
Log::Comment(L"Mouse wheel with respect to a window can't be checked on platform without classic window message queuing.");
|
|
Log::Result(WEX::Logging::TestResults::Skipped);
|
|
return;
|
|
}
|
|
|
|
Log::Comment(L"This test is flaky. Fix me in GH#4494");
|
|
Log::Result(WEX::Logging::TestResults::Skipped);
|
|
return;
|
|
|
|
//HWND const hwnd = GetConsoleWindow();
|
|
//VERIFY_IS_TRUE(!!IsWindow(hwnd), L"Get console window handle to inject wheel messages.");
|
|
|
|
//HANDLE const hConsoleInput = GetStdInputHandle();
|
|
//VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(hConsoleInput, dwConsoleMode), L"Apply the requested console mode");
|
|
|
|
//// We don't generate mouse console event in QuickEditMode or if MouseInput is not enabled
|
|
//DWORD dwExpectedEvents = 1;
|
|
//if (dwConsoleMode & ENABLE_QUICK_EDIT_MODE || !(dwConsoleMode & ENABLE_MOUSE_INPUT))
|
|
//{
|
|
// Log::Comment(L"QuickEditMode is set or MouseInput is not set, not expecting events");
|
|
// dwExpectedEvents = 0;
|
|
//}
|
|
|
|
//VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(hConsoleInput), L"Flush input queue to make sure no one else is in the way.");
|
|
|
|
//// WM_MOUSEWHEEL params
|
|
//// https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx
|
|
|
|
//// WPARAM is HIWORD the wheel delta and LOWORD the keystate (keys pressed with it)
|
|
//// We want no keys pressed in the loword (0) and we want one tick of the wheel in the high word.
|
|
//WPARAM wParam = 0;
|
|
//short sKeyState = 0;
|
|
//short sWheelDelta = -WHEEL_DELTA; // scroll down is negative, up is positive.
|
|
//// we only use the lower 32-bits (in case of 64-bit system)
|
|
//wParam = ((sWheelDelta << 16) | sKeyState) & 0xFFFFFFFF;
|
|
|
|
//// LPARAM is positioning information. We don't care so we'll leave it 0x0
|
|
//LPARAM lParam = 0;
|
|
|
|
//Log::Comment(L"Send scroll down message into console window queue.");
|
|
//SendMessageW(hwnd, msg, wParam, lParam);
|
|
|
|
//Sleep(250); // give message time to sink in
|
|
|
|
//DWORD dwAvailable = 0;
|
|
//VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(hConsoleInput, &dwAvailable), L"Retrieve number of events in queue.");
|
|
//VERIFY_ARE_EQUAL(dwExpectedEvents, dwAvailable, NoThrowString().Format(L"We expected %i event from our scroll message.", dwExpectedEvents));
|
|
|
|
//INPUT_RECORD ir;
|
|
//DWORD dwRead = 0;
|
|
//if (dwExpectedEvents == 1)
|
|
//{
|
|
// VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInputW(hConsoleInput, &ir, 1, &dwRead), L"Read the event out.");
|
|
// VERIFY_ARE_EQUAL(1u, dwRead);
|
|
|
|
// Log::Comment(L"Verify the event is what we expected. We only verify the fields relevant to this test.");
|
|
// VERIFY_ARE_EQUAL(MOUSE_EVENT, ir.EventType);
|
|
// // hard cast OK. only using lower 32-bits (see above)
|
|
// VERIFY_ARE_EQUAL((DWORD)wParam, ir.Event.MouseEvent.dwButtonState);
|
|
// // Don't care about ctrl key state. Can be messed with by caps lock/numlock state. Not checking this.
|
|
// VERIFY_ARE_EQUAL(dwEventFlagsExpected, ir.Event.MouseEvent.dwEventFlags);
|
|
// // Don't care about mouse position for ensuring scroll message went through.
|
|
//}
|
|
}
|
|
|
|
void InputTests::TestMouseWheelReadConsoleMouseInput()
|
|
{
|
|
const DWORD dwInputMode = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS;
|
|
TestMouseWheelReadConsoleInputHelper(WM_MOUSEWHEEL, MOUSE_WHEELED, dwInputMode);
|
|
}
|
|
|
|
void InputTests::TestMouseHorizWheelReadConsoleMouseInput()
|
|
{
|
|
const DWORD dwInputMode = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS;
|
|
TestMouseWheelReadConsoleInputHelper(WM_MOUSEHWHEEL, MOUSE_HWHEELED, dwInputMode);
|
|
}
|
|
|
|
void InputTests::TestMouseWheelReadConsoleNoMouseInput()
|
|
{
|
|
const DWORD dwInputMode = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_EXTENDED_FLAGS;
|
|
TestMouseWheelReadConsoleInputHelper(WM_MOUSEWHEEL, MOUSE_WHEELED, dwInputMode);
|
|
}
|
|
|
|
void InputTests::TestMouseHorizWheelReadConsoleNoMouseInput()
|
|
{
|
|
const DWORD dwInputMode = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_EXTENDED_FLAGS;
|
|
TestMouseWheelReadConsoleInputHelper(WM_MOUSEHWHEEL, MOUSE_HWHEELED, dwInputMode);
|
|
}
|
|
|
|
void InputTests::TestMouseWheelReadConsoleInputQuickEdit()
|
|
{
|
|
const DWORD dwInputMode = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS | ENABLE_INSERT_MODE | ENABLE_QUICK_EDIT_MODE;
|
|
TestMouseWheelReadConsoleInputHelper(WM_MOUSEWHEEL, MOUSE_WHEELED, dwInputMode);
|
|
}
|
|
|
|
void InputTests::TestMouseHorizWheelReadConsoleInputQuickEdit()
|
|
{
|
|
const DWORD dwInputMode = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS | ENABLE_INSERT_MODE | ENABLE_QUICK_EDIT_MODE;
|
|
TestMouseWheelReadConsoleInputHelper(WM_MOUSEHWHEEL, MOUSE_HWHEELED, dwInputMode);
|
|
}
|
|
|
|
void InputTests::TestReadWaitOnHandle()
|
|
{
|
|
HANDLE const hIn = GetStdInputHandle();
|
|
VERIFY_IS_NOT_NULL(hIn, L"Check input handle is not null.");
|
|
|
|
// Set up events and background thread to wait.
|
|
bool fAbortWait = false;
|
|
|
|
// this will be signaled when we want the thread to start waiting on the input handle
|
|
// It is an auto-reset.
|
|
wil::unique_event doWait;
|
|
doWait.create();
|
|
|
|
// the thread will signal this when it is done waiting on the input handle.
|
|
// It is an auto-reset.
|
|
wil::unique_event doneWaiting;
|
|
doneWaiting.create();
|
|
|
|
std::thread bgThread([&] {
|
|
while (!fAbortWait)
|
|
{
|
|
doWait.wait();
|
|
|
|
if (fAbortWait)
|
|
{
|
|
break;
|
|
}
|
|
|
|
HANDLE waits[2];
|
|
waits[0] = doWait.get();
|
|
waits[1] = hIn;
|
|
WaitForMultipleObjects(2, waits, FALSE, INFINITE);
|
|
|
|
if (fAbortWait)
|
|
{
|
|
break;
|
|
}
|
|
|
|
doneWaiting.SetEvent();
|
|
}
|
|
});
|
|
|
|
auto onExit = wil::scope_exit([&] {
|
|
Log::Comment(L"Tell our background thread to abort waiting, signal it, then wait for it to exit before we finish the test.");
|
|
fAbortWait = true;
|
|
doWait.SetEvent();
|
|
bgThread.join();
|
|
});
|
|
|
|
Log::Comment(L"Test 1: Waiting for text to be appended to the buffer.");
|
|
// Empty the buffer and tell the thread to start waiting
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(hIn), L"Ensure input buffer is empty.");
|
|
doWait.SetEvent();
|
|
|
|
// Send some input into the console.
|
|
INPUT_RECORD ir;
|
|
ir.EventType = MOUSE_EVENT;
|
|
ir.Event.MouseEvent.dwMousePosition.X = 1;
|
|
ir.Event.MouseEvent.dwMousePosition.Y = 1;
|
|
ir.Event.MouseEvent.dwButtonState = FROM_LEFT_1ST_BUTTON_PRESSED;
|
|
ir.Event.MouseEvent.dwControlKeyState = NUMLOCK_ON;
|
|
ir.Event.MouseEvent.dwEventFlags = 0;
|
|
|
|
DWORD dwWritten = 0;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputW(hIn, &ir, 1, &dwWritten), L"Inject input event into queue.");
|
|
VERIFY_ARE_EQUAL(1u, dwWritten, L"Ensure 1 event was written.");
|
|
|
|
VERIFY_IS_TRUE(doneWaiting.wait(5000), L"The input handle should have been signaled on our background thread within our 5 second timeout.");
|
|
|
|
Log::Comment(L"Test 2: Trigger a VT response so the buffer will be prepended (things inserted at the front).");
|
|
|
|
HANDLE const hOut = GetStdOutputHandle();
|
|
DWORD dwMode = 0;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(hOut, &dwMode), L"Get existing console mode.");
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING), L"Ensure VT mode is on.");
|
|
|
|
// Empty the buffer and tell the thread to start waiting
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(hIn), L"Ensure input buffer is empty.");
|
|
doWait.SetEvent();
|
|
|
|
// Send a VT command that will trigger a response.
|
|
PCWSTR pwszDeviceAttributeRequest = L"\x1b[c";
|
|
DWORD cchDeviceAttributeRequest = static_cast<DWORD>(wcslen(pwszDeviceAttributeRequest));
|
|
dwWritten = 0;
|
|
WriteConsoleW(hOut, pwszDeviceAttributeRequest, cchDeviceAttributeRequest, &dwWritten, nullptr);
|
|
VERIFY_ARE_EQUAL(cchDeviceAttributeRequest, dwWritten, L"Verify string was written");
|
|
|
|
VERIFY_IS_TRUE(doneWaiting.wait(5000), L"The input handle should have been signaled on our background thread within our 5 second timeout.");
|
|
}
|
|
|
|
void InputTests::TestVtInputGeneration()
|
|
{
|
|
Log::Comment(L"Get input handle");
|
|
HANDLE hIn = GetStdInputHandle();
|
|
|
|
DWORD dwMode;
|
|
GetConsoleMode(hIn, &dwMode);
|
|
|
|
DWORD dwWritten = (DWORD)-1;
|
|
DWORD dwRead = (DWORD)-1;
|
|
INPUT_RECORD rgInputRecords[64] = { 0 };
|
|
|
|
Log::Comment(L"First make sure that an arrow keydown is not translated in not-VT mode");
|
|
|
|
dwMode = WI_ClearFlag(dwMode, ENABLE_VIRTUAL_TERMINAL_INPUT);
|
|
SetConsoleMode(hIn, dwMode);
|
|
GetConsoleMode(hIn, &dwMode);
|
|
VERIFY_IS_FALSE(WI_IsFlagSet(dwMode, ENABLE_VIRTUAL_TERMINAL_INPUT));
|
|
|
|
rgInputRecords[0].EventType = KEY_EVENT;
|
|
rgInputRecords[0].Event.KeyEvent.bKeyDown = TRUE;
|
|
rgInputRecords[0].Event.KeyEvent.wRepeatCount = 1;
|
|
rgInputRecords[0].Event.KeyEvent.wVirtualKeyCode = VK_UP;
|
|
|
|
Log::Comment(L"Writing events");
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInput(hIn, rgInputRecords, 1, &dwWritten));
|
|
VERIFY_ARE_EQUAL(dwWritten, (DWORD)1);
|
|
|
|
Log::Comment(L"Reading events");
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInput(hIn, rgInputRecords, ARRAYSIZE(rgInputRecords), &dwRead));
|
|
VERIFY_ARE_EQUAL(dwRead, (DWORD)1);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[0].EventType, KEY_EVENT);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[0].Event.KeyEvent.bKeyDown, TRUE);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[0].Event.KeyEvent.wVirtualKeyCode, VK_UP);
|
|
|
|
Log::Comment(L"Now, enable VT Input and make sure that a vt sequence comes out the other side.");
|
|
|
|
dwMode = WI_SetFlag(dwMode, ENABLE_VIRTUAL_TERMINAL_INPUT);
|
|
SetConsoleMode(hIn, dwMode);
|
|
GetConsoleMode(hIn, &dwMode);
|
|
VERIFY_IS_TRUE(WI_IsFlagSet(dwMode, ENABLE_VIRTUAL_TERMINAL_INPUT));
|
|
|
|
Log::Comment(L"Flushing");
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(hIn));
|
|
|
|
rgInputRecords[0].EventType = KEY_EVENT;
|
|
rgInputRecords[0].Event.KeyEvent.bKeyDown = TRUE;
|
|
rgInputRecords[0].Event.KeyEvent.wRepeatCount = 1;
|
|
rgInputRecords[0].Event.KeyEvent.wVirtualKeyCode = VK_UP;
|
|
|
|
Log::Comment(L"Writing events");
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInput(hIn, rgInputRecords, 1, &dwWritten));
|
|
VERIFY_ARE_EQUAL(dwWritten, (DWORD)1);
|
|
|
|
Log::Comment(L"Reading events");
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInput(hIn, rgInputRecords, ARRAYSIZE(rgInputRecords), &dwRead));
|
|
VERIFY_ARE_EQUAL(dwRead, (DWORD)3);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[0].EventType, KEY_EVENT);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[0].Event.KeyEvent.bKeyDown, TRUE);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[0].Event.KeyEvent.wVirtualKeyCode, 0);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[0].Event.KeyEvent.uChar.UnicodeChar, L'\x1b');
|
|
|
|
VERIFY_ARE_EQUAL(rgInputRecords[1].EventType, KEY_EVENT);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[1].Event.KeyEvent.bKeyDown, TRUE);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[1].Event.KeyEvent.wVirtualKeyCode, 0);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[1].Event.KeyEvent.uChar.UnicodeChar, L'[');
|
|
|
|
VERIFY_ARE_EQUAL(rgInputRecords[2].EventType, KEY_EVENT);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[2].Event.KeyEvent.bKeyDown, TRUE);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[2].Event.KeyEvent.wVirtualKeyCode, 0);
|
|
VERIFY_ARE_EQUAL(rgInputRecords[2].Event.KeyEvent.uChar.UnicodeChar, L'A');
|
|
}
|
|
|
|
void InputTests::RawReadUnpacksCoalescedInputRecords()
|
|
{
|
|
DWORD mode = 0;
|
|
HANDLE hIn = GetStdInputHandle();
|
|
const wchar_t writeWch = L'a';
|
|
const auto repeatCount = 5;
|
|
|
|
// turn on raw mode
|
|
GetConsoleMode(hIn, &mode);
|
|
WI_ClearFlag(mode, ENABLE_LINE_INPUT);
|
|
SetConsoleMode(hIn, mode);
|
|
|
|
// flush input queue before attempting to add new events and check
|
|
// in case any are leftover from previous tests
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(hIn));
|
|
|
|
INPUT_RECORD record;
|
|
record.EventType = KEY_EVENT;
|
|
record.Event.KeyEvent.bKeyDown = TRUE;
|
|
record.Event.KeyEvent.wRepeatCount = repeatCount;
|
|
record.Event.KeyEvent.wVirtualKeyCode = writeWch;
|
|
record.Event.KeyEvent.uChar.UnicodeChar = writeWch;
|
|
|
|
// write an event with a repeat count
|
|
DWORD writtenAmount = 0;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInput(hIn, &record, 1, &writtenAmount));
|
|
VERIFY_ARE_EQUAL(writtenAmount, static_cast<DWORD>(1));
|
|
|
|
// stream read the events out one at a time
|
|
DWORD eventCount = 0;
|
|
for (int i = 0; i < repeatCount; ++i)
|
|
{
|
|
eventCount = 0;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(hIn, &eventCount));
|
|
VERIFY_IS_TRUE(eventCount > 0);
|
|
|
|
wchar_t wch;
|
|
DWORD readAmount;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsole(hIn, &wch, 1, &readAmount, NULL));
|
|
VERIFY_ARE_EQUAL(readAmount, static_cast<DWORD>(1));
|
|
VERIFY_ARE_EQUAL(wch, writeWch);
|
|
}
|
|
|
|
// input buffer should now be empty
|
|
eventCount = 0;
|
|
VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(hIn, &eventCount));
|
|
VERIFY_ARE_EQUAL(eventCount, static_cast<DWORD>(0));
|
|
}
|