// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "..\..\inc\consoletaeftemplates.hpp" #include #include #include #include #include #define KEY_STATE_TOGGLED (0x1) #define KEY_STATE_PRESSED (0x80) #define KEY_STATE_RELEASED (0x0) #define KEY_MESSAGE_CONTEXT_CODE (0x20000000) #define KEY_MESSAGE_UPKEY_CODE (0xC0000000) #define SINGLE_KEY_REPEAT (0x00000001) #define EXTENDED_KEY_FLAG (0x01000000) #define SLEEP_WAIT_TIME (2 * 1000) #define GERMAN_KEYBOARD_LAYOUT (MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN)) using namespace WEX::TestExecution; using namespace WEX::Common; using WEX::Logging::Log; class KeyPressTests { BEGIN_TEST_CLASS(KeyPressTests) END_TEST_CLASS() void TurnOffModifierKeys(HWND hwnd) { // these are taken from GetControlKeyState. static const WPARAM modifiers[8] = { VK_LMENU, VK_RMENU, VK_LCONTROL, VK_RCONTROL, VK_SHIFT, VK_NUMLOCK, VK_SCROLL, VK_CAPITAL }; for (unsigned int i = 0; i < 8; ++i) { PostMessage(hwnd, CM_SET_KEY_STATE, modifiers[i], KEY_STATE_RELEASED); } } TEST_METHOD(TestContextMenuKey) { if (!OneCoreDelay::IsPostMessageWPresent()) { Log::Comment(L"Injecting keys to the window message queue cannot be done on systems without a classic window message queue. Skipping."); Log::Result(WEX::Logging::TestResults::Skipped); return; } Log::Comment(L"Checks that the context menu key is correctly added to the input buffer."); Log::Comment(L"This test will fail on some keyboard layouts. Ensure you're using a QWERTY keyboard if " L"you're encountering a test failure here."); HWND hwnd = GetConsoleWindow(); VERIFY_IS_TRUE(!!IsWindow(hwnd)); HANDLE inputHandle = GetStdHandle(STD_INPUT_HANDLE); DWORD events = 0; // flush input buffer FlushConsoleInputBuffer(inputHandle); VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(inputHandle, &events)); VERIFY_ARE_EQUAL(events, 0u); // send context menu key event TurnOffModifierKeys(hwnd); Sleep(SLEEP_WAIT_TIME); UINT scanCode = MapVirtualKeyW(VK_APPS, MAPVK_VK_TO_VSC); PostMessageW(hwnd, WM_KEYDOWN, VK_APPS, EXTENDED_KEY_FLAG | SINGLE_KEY_REPEAT | (scanCode << 16)); Sleep(SLEEP_WAIT_TIME); INPUT_RECORD expectedRecord; expectedRecord.EventType = KEY_EVENT; 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; // get the input record back and test it INPUT_RECORD record; VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInputW(inputHandle, &record, 1, &events)); VERIFY_IS_GREATER_THAN(events, 0u); VERIFY_ARE_EQUAL(expectedRecord, record); } BEGIN_TEST_METHOD(TestAltGr) TEST_METHOD_PROPERTY(L"Ignore[@DevTest=true]", L"false") TEST_METHOD_PROPERTY(L"Ignore[default]", L"true") END_TEST_METHOD() TEST_METHOD(TestCoalesceSameKeyPress) { if (!OneCoreDelay::IsSendMessageWPresent()) { Log::Comment(L"Injecting keys to the window message queue cannot be done on systems without a classic window message queue. Skipping."); Log::Result(WEX::Logging::TestResults::Skipped); return; } Log::Comment(L"Testing that key events are properly coalesced when the same key is pressed repeatedly"); BOOL successBool; HWND hwnd = GetConsoleWindow(); VERIFY_IS_TRUE(!!IsWindow(hwnd)); HANDLE inputHandle = GetStdHandle(STD_INPUT_HANDLE); DWORD events = 0; // flush input buffer FlushConsoleInputBuffer(inputHandle); successBool = GetNumberOfConsoleInputEvents(inputHandle, &events); VERIFY_IS_TRUE(!!successBool); VERIFY_ARE_EQUAL(events, 0u); // send a bunch of 'a' keypresses to the console DWORD repeatCount = 1; const unsigned int messageSendCount = 1000; for (unsigned int i = 0; i < messageSendCount; ++i) { SendMessage(hwnd, WM_CHAR, 0x41, repeatCount); } // make sure the the keypresses got processed and coalesced events = 0; successBool = GetNumberOfConsoleInputEvents(inputHandle, &events); VERIFY_IS_TRUE(!!successBool); VERIFY_IS_GREATER_THAN(events, 0u, NoThrowString().Format(L"%d", events)); std::unique_ptr inputBuffer = std::make_unique(1); PeekConsoleInput(inputHandle, inputBuffer.get(), 1, &events); VERIFY_ARE_EQUAL(events, 1u); VERIFY_ARE_EQUAL(inputBuffer[0].EventType, KEY_EVENT); VERIFY_ARE_EQUAL(inputBuffer[0].Event.KeyEvent.wRepeatCount, messageSendCount, NoThrowString().Format(L"%d", inputBuffer[0].Event.KeyEvent.wRepeatCount)); } TEST_METHOD(TestCtrlKeyDownUp) { BEGIN_TEST_METHOD_PROPERTIES() // VKeys for A-Z // See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx TEST_METHOD_PROPERTY(L"Data:vKey", L"{" "0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F," "0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A" "}") END_TEST_METHOD_PROPERTIES(); if (!OneCoreDelay::IsSendMessageWPresent()) { Log::Comment(L"Ctrl key eventing scenario can't be checked on platform without window message queuing."); Log::Result(WEX::Logging::TestResults::Skipped); return; } UINT vk; VERIFY_SUCCEEDED(TestData::TryGetValue(L"vKey", vk)); Log::Comment(L"Testing the right number of input events is generated by Ctrl+Key press"); BOOL successBool; HWND hwnd = GetConsoleWindow(); VERIFY_IS_TRUE(!!IsWindow(hwnd)); HANDLE inputHandle = GetStdHandle(STD_INPUT_HANDLE); DWORD events = 0; // Set the console to raw mode, so that it doesn't hijack any keypresses as shortcut keys SetConsoleMode(inputHandle, 0); // flush input buffer FlushConsoleInputBuffer(inputHandle); VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(inputHandle, &events)); VERIFY_ARE_EQUAL(events, 0u); DWORD dwInMode = 0; GetConsoleMode(inputHandle, &dwInMode); Log::Comment(NoThrowString().Format(L"Mode:0x%x", dwInMode)); UINT vkCtrl = VK_LCONTROL; // Need this instead of VK_CONTROL UINT uiCtrlScancode = MapVirtualKey(vkCtrl, MAPVK_VK_TO_VSC); // According to // KEY_KEYDOWN https://msdn.microsoft.com/en-us/library/windows/desktop/ms646280(v=vs.85).aspx // KEY_UP https://msdn.microsoft.com/en-us/library/windows/desktop/ms646281(v=vs.85).aspx LPARAM CtrlFlags = (LOBYTE(uiCtrlScancode) << 16) | SINGLE_KEY_REPEAT; LPARAM CtrlUpFlags = CtrlFlags | KEY_MESSAGE_UPKEY_CODE; UINT uiScancode = MapVirtualKey(vk, MAPVK_VK_TO_VSC); LPARAM DownFlags = (LOBYTE(uiScancode) << 16) | SINGLE_KEY_REPEAT; LPARAM UpFlags = DownFlags | KEY_MESSAGE_UPKEY_CODE; Log::Comment(NoThrowString().Format(L"Testing Ctrl+%c", vk)); Log::Comment(NoThrowString().Format(L"DownFlags=0x%x, CtrlFlags=0x%x", DownFlags, CtrlFlags)); Log::Comment(NoThrowString().Format(L"UpFlags=0x%x, CtrlUpFlags=0x%x", UpFlags, CtrlUpFlags)); // Don't Use PostMessage, those events come in the wrong order. // Also can't use SendInput because of the whole test window backgrounding thing. // It'd work locally, until you minimize the window. SendMessage(hwnd, WM_KEYDOWN, vkCtrl, CtrlFlags); SendMessage(hwnd, WM_KEYDOWN, vk, DownFlags); SendMessage(hwnd, WM_KEYUP, vk, UpFlags); SendMessage(hwnd, WM_KEYUP, vkCtrl, CtrlUpFlags); Sleep(50); events = 0; successBool = GetNumberOfConsoleInputEvents(inputHandle, &events); VERIFY_IS_TRUE(!!successBool); VERIFY_IS_GREATER_THAN(events, 0u, NoThrowString().Format(L"%d events found", events)); std::unique_ptr inputBuffer = std::make_unique(16); PeekConsoleInput(inputHandle, inputBuffer.get(), 16, &events); for (size_t i = 0; i < events; i++) { INPUT_RECORD rc = inputBuffer[i]; switch (rc.EventType) { case KEY_EVENT: { Log::Comment(NoThrowString().Format( L"Down: %d Repeat: %d KeyCode: 0x%x ScanCode: 0x%x Char: %c (0x%x) KeyState: 0x%x", rc.Event.KeyEvent.bKeyDown, rc.Event.KeyEvent.wRepeatCount, rc.Event.KeyEvent.wVirtualKeyCode, rc.Event.KeyEvent.wVirtualScanCode, rc.Event.KeyEvent.uChar.UnicodeChar != 0 ? rc.Event.KeyEvent.uChar.UnicodeChar : ' ', rc.Event.KeyEvent.uChar.UnicodeChar, rc.Event.KeyEvent.dwControlKeyState)); break; } default: Log::Comment(NoThrowString().Format(L"Another event type was found.")); } } VERIFY_ARE_EQUAL(events, 4u); VERIFY_ARE_EQUAL(inputBuffer[0].EventType, KEY_EVENT); VERIFY_ARE_EQUAL(inputBuffer[1].EventType, KEY_EVENT); VERIFY_ARE_EQUAL(inputBuffer[2].EventType, KEY_EVENT); VERIFY_ARE_EQUAL(inputBuffer[3].EventType, KEY_EVENT); FlushConsoleInputBuffer(inputHandle); } TEST_METHOD(TestMaximize) { if (!OneCoreDelay::IsSendMessageWPresent()) { Log::Comment(L"Injecting keys to the window message queue cannot be done on systems without a classic window message queue. Skipping."); Log::Result(WEX::Logging::TestResults::Skipped); return; } const HANDLE inputHandle = GetStdHandle(STD_INPUT_HANDLE); const HWND hwnd = GetConsoleWindow(); VERIFY_IS_TRUE(!!IsWindow(hwnd)); // Need the console to be in processed input for this to work SetConsoleMode(inputHandle, ENABLE_PROCESSED_INPUT); FlushConsoleInputBuffer(inputHandle); LONG oldStyle = GetWindowLongW(hwnd, GWL_STYLE); LONG oldExStyle = GetWindowLongW(hwnd, GWL_EXSTYLE); // According to // KEY_KEYDOWN https://msdn.microsoft.com/en-us/library/windows/desktop/ms646280(v=vs.85).aspx // KEY_UP https://msdn.microsoft.com/en-us/library/windows/desktop/ms646281(v=vs.85).aspx const UINT vsc = MapVirtualKey(VK_F11, MAPVK_VK_TO_VSC); const LPARAM F11Flags = (LOBYTE(vsc) << 16) | SINGLE_KEY_REPEAT; const LPARAM F11UpFlags = F11Flags | KEY_MESSAGE_UPKEY_CODE; // Send F11 key down and up. lParam is VirtualScanCode and RepeatCount SendMessage(hwnd, WM_KEYDOWN, VK_F11, F11Flags); SendMessage(hwnd, WM_KEYUP, VK_F11, F11UpFlags); LONG maxStyle = GetWindowLongW(hwnd, GWL_STYLE); LONG maxExStyle = GetWindowLongW(hwnd, GWL_EXSTYLE); // Send F11 key down and up. lParam is VirtualScanCode and RepeatCount SendMessage(hwnd, WM_KEYDOWN, VK_F11, F11Flags); SendMessage(hwnd, WM_KEYUP, VK_F11, F11UpFlags); LONG newStyle = GetWindowLongW(hwnd, GWL_STYLE); LONG newExStyle = GetWindowLongW(hwnd, GWL_EXSTYLE); // Maximize windows should not be Overlapped & have a popup // Extended style should have a window edge when not maximized VERIFY_IS_TRUE(WI_IsFlagSet(maxStyle, WS_POPUP)); VERIFY_IS_TRUE(WI_AreAllFlagsClear(maxStyle, WS_OVERLAPPEDWINDOW)); VERIFY_IS_TRUE(WI_IsFlagClear(maxExStyle, WS_EX_WINDOWEDGE)); VERIFY_IS_TRUE(WI_IsFlagClear(newStyle, WS_POPUP)); VERIFY_IS_TRUE(WI_AreAllFlagsSet(newStyle, WS_OVERLAPPEDWINDOW)); VERIFY_IS_TRUE(WI_IsFlagSet(newExStyle, WS_EX_WINDOWEDGE)); VERIFY_ARE_NOT_EQUAL(maxStyle, oldStyle); VERIFY_ARE_NOT_EQUAL(maxExStyle, oldExStyle); // Ignore the scrollbars when comparing styles WI_ClearAllFlags(oldStyle, WS_HSCROLL | WS_VSCROLL); WI_ClearAllFlags(newStyle, WS_HSCROLL | WS_VSCROLL); VERIFY_ARE_EQUAL(oldStyle, newStyle); VERIFY_ARE_EQUAL(oldExStyle, newExStyle); } }; void KeyPressTests::TestAltGr() { Log::Comment(L"Checks that alt-gr behavior is maintained."); HWND hwnd = GetConsoleWindow(); VERIFY_IS_TRUE(!!IsWindow(hwnd)); HANDLE inputHandle = GetStdHandle(STD_INPUT_HANDLE); DWORD events = 0; // flush input buffer FlushConsoleInputBuffer(inputHandle); VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(inputHandle, &events)); VERIFY_ARE_EQUAL(events, 0u); // create german locale string std::wstringstream wss; wss << std::setfill(L'0') << std::setw(8) << std::hex << GERMAN_KEYBOARD_LAYOUT; std::wstring germanKeyboardLayoutString(wss.str()); // save current keyboard layout wchar_t originalLocaleId[KL_NAMELENGTH]; GetKeyboardLayoutName(originalLocaleId); // make console window the topmost window SetForegroundWindow(hwnd); // change to german keyboard layout PostMessage(hwnd, CM_SET_KEYBOARD_LAYOUT, std::stoi(germanKeyboardLayoutString), NULL); Sleep(SLEEP_WAIT_TIME); LoadKeyboardLayout(germanKeyboardLayoutString.c_str(), KLF_ACTIVATE); // turn off all modifier keys TurnOffModifierKeys(hwnd); // set right control key to be pressed PostMessage(hwnd, CM_SET_KEY_STATE, VK_LCONTROL, KEY_STATE_PRESSED); PostMessage(hwnd, CM_SET_KEY_STATE, VK_CONTROL, KEY_STATE_PRESSED); // set right alt to be pressed PostMessage(hwnd, CM_SET_KEY_STATE, VK_RMENU, KEY_STATE_PRESSED); PostMessage(hwnd, CM_SET_KEY_STATE, VK_MENU, KEY_STATE_PRESSED); Sleep(SLEEP_WAIT_TIME); // flush input buffer in preparation of the key event FlushConsoleInputBuffer(inputHandle); VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(inputHandle, &events)); VERIFY_ARE_EQUAL(events, 0u); // send the key event that will be turned into an '@' UINT scanCode = MapVirtualKey('Q', MAPVK_VK_TO_VSC); PostMessage(hwnd, WM_KEYDOWN, 'Q', KEY_MESSAGE_CONTEXT_CODE | SINGLE_KEY_REPEAT | (scanCode << 16)); Sleep(SLEEP_WAIT_TIME); // reset the keymap TurnOffModifierKeys(hwnd); // create expected input record INPUT_RECORD expectedRecord; expectedRecord.EventType = KEY_EVENT; expectedRecord.Event.KeyEvent.uChar.UnicodeChar = L'@'; expectedRecord.Event.KeyEvent.bKeyDown = true; expectedRecord.Event.KeyEvent.dwControlKeyState = RIGHT_ALT_PRESSED | LEFT_CTRL_PRESSED; expectedRecord.Event.KeyEvent.wRepeatCount = SINGLE_KEY_REPEAT; expectedRecord.Event.KeyEvent.wVirtualKeyCode = L'Q'; expectedRecord.Event.KeyEvent.wVirtualScanCode = (WORD)scanCode; // read input records and compare const int maxRecordLookup = 20; // some arbitrary value to grab some records Log::Comment(L"Looking for input record matching:"); Log::Comment(VerifyOutputTraits::ToString(expectedRecord)); INPUT_RECORD records[20]; VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInput(inputHandle, records, maxRecordLookup, &events)); VERIFY_IS_GREATER_THAN(events, 0u); bool successBool = false; // look for the expected record somewhere in the returned records for (unsigned int i = 0; i < events; ++i) { Log::Comment(VerifyOutputTraits::ToString(records[i])); if (VerifyCompareTraits::AreEqual(records[i], expectedRecord)) { successBool = true; break; } } VERIFY_IS_TRUE(successBool); // reset the keyboard layout WPARAM originalLocale; std::wstringstream localeStringStream(originalLocaleId); localeStringStream >> std::hex >> originalLocale; PostMessage(hwnd, CM_SET_KEYBOARD_LAYOUT, originalLocale, NULL); LoadKeyboardLayout(originalLocaleId, KLF_ACTIVATE | KLF_SUBSTITUTE_OK); }