416 lines
17 KiB
C++
416 lines
17 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "../../inc/consoletaeftemplates.hpp"
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
|
|
#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<INPUT_RECORD[]> inputBuffer = std::make_unique<INPUT_RECORD[]>(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<INPUT_RECORD[]> inputBuffer = std::make_unique<INPUT_RECORD[]>(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<INPUT_RECORD>::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<INPUT_RECORD>::ToString(records[i]));
|
|
if (VerifyCompareTraits<INPUT_RECORD, INPUT_RECORD>::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);
|
|
}
|