329 lines
14 KiB
C++
329 lines
14 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
#include "WexTestClass.h"
|
|
|
|
#include "CommonState.hpp"
|
|
|
|
#include "globals.h"
|
|
|
|
#include "../interactivity/win32/Clipboard.hpp"
|
|
#include "../interactivity/inc/ServiceLocator.hpp"
|
|
|
|
#include "dbcs.h"
|
|
|
|
#include <cctype>
|
|
|
|
#ifdef BUILD_ONECORE_INTERACTIVITY
|
|
#include "../../interactivity/inc/VtApiRedirection.hpp"
|
|
#endif
|
|
|
|
#include "../../inc/consoletaeftemplates.hpp"
|
|
|
|
using namespace WEX::Common;
|
|
using namespace WEX::Logging;
|
|
using namespace WEX::TestExecution;
|
|
using namespace Microsoft::Console::Interactivity;
|
|
using namespace Microsoft::Console::Interactivity::Win32;
|
|
|
|
static const WORD altScanCode = 0x38;
|
|
static const WORD leftShiftScanCode = 0x2A;
|
|
|
|
class ClipboardTests
|
|
{
|
|
TEST_CLASS(ClipboardTests);
|
|
|
|
CommonState* m_state;
|
|
|
|
TEST_CLASS_SETUP(ClassSetup)
|
|
{
|
|
m_state = new CommonState();
|
|
|
|
m_state->PrepareGlobalFont();
|
|
m_state->PrepareGlobalScreenBuffer();
|
|
m_state->PrepareGlobalInputBuffer();
|
|
|
|
return true;
|
|
}
|
|
|
|
TEST_CLASS_CLEANUP(ClassCleanup)
|
|
{
|
|
m_state->CleanupGlobalInputBuffer();
|
|
m_state->CleanupGlobalScreenBuffer();
|
|
m_state->CleanupGlobalFont();
|
|
|
|
return true;
|
|
}
|
|
|
|
TEST_METHOD_SETUP(MethodSetup)
|
|
{
|
|
m_state->FillTextBuffer();
|
|
return true;
|
|
}
|
|
|
|
TEST_METHOD_CLEANUP(MethodCleanup)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const UINT cRectsSelected = 4;
|
|
|
|
std::vector<std::wstring> SetupRetrieveFromBuffers(bool fLineSelection, std::vector<SMALL_RECT>& selection)
|
|
{
|
|
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
// NOTE: This test requires innate knowledge of how the common buffer text is emitted in order to test all cases
|
|
// Please see CommonState.hpp for information on the buffer state per row, the row contents, etc.
|
|
|
|
// set up and try to retrieve the first 4 rows from the buffer
|
|
const auto& screenInfo = gci.GetActiveOutputBuffer();
|
|
|
|
selection.clear();
|
|
selection.emplace_back(SMALL_RECT{ 0, 0, 8, 0 });
|
|
selection.emplace_back(SMALL_RECT{ 0, 1, 14, 1 });
|
|
selection.emplace_back(SMALL_RECT{ 0, 2, 14, 2 });
|
|
selection.emplace_back(SMALL_RECT{ 0, 3, 8, 3 });
|
|
|
|
const auto& buffer = screenInfo.GetTextBuffer();
|
|
return buffer.GetText(true, fLineSelection, selection).text;
|
|
}
|
|
|
|
#pragma prefast(push)
|
|
#pragma prefast(disable : 26006, "Specifically trying to check unterminated strings in this test.")
|
|
TEST_METHOD(TestRetrieveFromBuffer)
|
|
{
|
|
// NOTE: This test requires innate knowledge of how the common buffer text is emitted in order to test all cases
|
|
// Please see CommonState.hpp for information on the buffer state per row, the row contents, etc.
|
|
|
|
std::vector<SMALL_RECT> selection;
|
|
const auto text = SetupRetrieveFromBuffers(false, selection);
|
|
|
|
// verify trailing bytes were trimmed
|
|
// there are 2 double-byte characters in our sample string (see CommonState.hpp for sample)
|
|
// the width is right - left
|
|
VERIFY_ARE_EQUAL((short)wcslen(text[0].data()), selection[0].Right - selection[0].Left + 1);
|
|
|
|
// since we're not in line selection, the line should be \r\n terminated
|
|
PCWCHAR tempPtr = text[0].data();
|
|
tempPtr += text[0].size();
|
|
tempPtr -= 2;
|
|
VERIFY_ARE_EQUAL(String(tempPtr), String(L"\r\n"));
|
|
|
|
// since we're not in line selection, spaces should be trimmed from the end
|
|
tempPtr = text[0].data();
|
|
tempPtr += selection[0].Right - selection[0].Left - 2;
|
|
tempPtr++;
|
|
VERIFY_IS_NULL(wcsrchr(tempPtr, L' '));
|
|
|
|
// final line of selection should not contain CR/LF
|
|
tempPtr = text[3].data();
|
|
tempPtr += text[3].size();
|
|
tempPtr -= 2;
|
|
VERIFY_ARE_NOT_EQUAL(String(tempPtr), String(L"\r\n"));
|
|
}
|
|
#pragma prefast(pop)
|
|
|
|
TEST_METHOD(TestRetrieveLineSelectionFromBuffer)
|
|
{
|
|
// NOTE: This test requires innate knowledge of how the common buffer text is emitted in order to test all cases
|
|
// Please see CommonState.hpp for information on the buffer state per row, the row contents, etc.
|
|
|
|
std::vector<SMALL_RECT> selection;
|
|
const auto text = SetupRetrieveFromBuffers(true, selection);
|
|
|
|
// row 2, no wrap
|
|
// no wrap row before the end should have CR/LF
|
|
PCWCHAR tempPtr = text[2].data();
|
|
tempPtr += text[2].size();
|
|
tempPtr -= 2;
|
|
VERIFY_ARE_EQUAL(String(tempPtr), String(L"\r\n"));
|
|
|
|
// no wrap row should trim spaces at the end
|
|
tempPtr = text[2].data();
|
|
VERIFY_IS_NULL(wcsrchr(tempPtr, L' '));
|
|
|
|
// row 1, wrap
|
|
// wrap row before the end should *not* have CR/LF
|
|
tempPtr = text[1].data();
|
|
tempPtr += text[1].size();
|
|
tempPtr -= 2;
|
|
VERIFY_ARE_NOT_EQUAL(String(tempPtr), String(L"\r\n"));
|
|
|
|
// wrap row should have spaces at the end
|
|
tempPtr = text[1].data();
|
|
const wchar_t* ptr = wcsrchr(tempPtr, L' ');
|
|
VERIFY_IS_NOT_NULL(ptr);
|
|
}
|
|
|
|
TEST_METHOD(CanConvertTextToInputEvents)
|
|
{
|
|
std::wstring wstr = L"hello world";
|
|
std::deque<std::unique_ptr<IInputEvent>> events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
|
|
wstr.size());
|
|
VERIFY_ARE_EQUAL(wstr.size() * 2, events.size());
|
|
IInputServices* pInputServices = ServiceLocator::LocateInputServices();
|
|
for (wchar_t wch : wstr)
|
|
{
|
|
std::deque<bool> keydownPattern{ true, false };
|
|
for (bool isKeyDown : keydownPattern)
|
|
{
|
|
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 keyState = pInputServices->VkKeyScanW(wch);
|
|
VERIFY_ARE_NOT_EQUAL(-1, keyState);
|
|
const WORD virtualScanCode = static_cast<WORD>(pInputServices->MapVirtualKeyW(LOBYTE(keyState), MAPVK_VK_TO_VSC));
|
|
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(CanConvertUppercaseText)
|
|
{
|
|
std::wstring wstr = L"HeLlO WoRlD";
|
|
size_t uppercaseCount = 0;
|
|
for (wchar_t wch : wstr)
|
|
{
|
|
std::isupper(wch) ? ++uppercaseCount : 0;
|
|
}
|
|
std::deque<std::unique_ptr<IInputEvent>> events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
|
|
wstr.size());
|
|
|
|
VERIFY_ARE_EQUAL((wstr.size() + uppercaseCount) * 2, events.size());
|
|
IInputServices* pInputServices = ServiceLocator::LocateInputServices();
|
|
VERIFY_IS_NOT_NULL(pInputServices);
|
|
for (wchar_t wch : wstr)
|
|
{
|
|
std::deque<bool> keydownPattern{ true, false };
|
|
for (bool 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 short keyState = pInputServices->VkKeyScanW(wch);
|
|
VERIFY_ARE_NOT_EQUAL(keyScanError, keyState);
|
|
const WORD virtualScanCode = static_cast<WORD>(pInputServices->MapVirtualKeyW(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 short keyState2 = pInputServices->VkKeyScanW(wch);
|
|
VERIFY_ARE_NOT_EQUAL(keyScanError, keyState2);
|
|
const WORD virtualScanCode2 = static_cast<WORD>(pInputServices->MapVirtualKeyW(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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef __INSIDE_WINDOWS
|
|
TEST_METHOD(CanConvertCharsRequiringAltGr)
|
|
{
|
|
const std::wstring wstr = L"\x20ac"; // € char U+20AC
|
|
std::deque<std::unique_ptr<IInputEvent>> events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
|
|
wstr.size());
|
|
|
|
// should be converted to:
|
|
// 1. AltGr keydown
|
|
// 2. € keydown
|
|
// 3. € keyup
|
|
// 4. AltGr keyup
|
|
const size_t convertedSize = 4;
|
|
VERIFY_ARE_EQUAL(convertedSize, events.size());
|
|
|
|
const short keyState = VkKeyScanW(wstr[0]);
|
|
const WORD virtualKeyCode = LOBYTE(keyState);
|
|
|
|
std::deque<KeyEvent> expectedEvents;
|
|
expectedEvents.push_back({ TRUE, 1, VK_MENU, altScanCode, L'\0', (ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) });
|
|
expectedEvents.push_back({ TRUE, 1, virtualKeyCode, 0, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) });
|
|
expectedEvents.push_back({ FALSE, 1, virtualKeyCode, 0, wstr[0], (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) });
|
|
expectedEvents.push_back({ FALSE, 1, VK_MENU, altScanCode, L'\0', ENHANCED_KEY });
|
|
|
|
for (size_t i = 0; i < events.size(); ++i)
|
|
{
|
|
const KeyEvent currentKeyEvent = *reinterpret_cast<const KeyEvent* const>(events[i].get());
|
|
VERIFY_ARE_EQUAL(expectedEvents[i], currentKeyEvent, NoThrowString().Format(L"i == %d", i));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TEST_METHOD(CanConvertCharsOutsideKeyboardLayout)
|
|
{
|
|
const std::wstring wstr = L"\xbc"; // ¼ char U+00BC
|
|
const UINT outputCodepage = CP_JAPANESE;
|
|
ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP = outputCodepage;
|
|
std::deque<std::unique_ptr<IInputEvent>> events = Clipboard::Instance().TextToKeyEvents(wstr.c_str(),
|
|
wstr.size());
|
|
|
|
// should be converted to:
|
|
// 1. left alt keydown
|
|
// 2. 1st numpad keydown
|
|
// 3. 1st numpad keyup
|
|
// 4. 2nd numpad keydown
|
|
// 5. 2nd numpad keyup
|
|
// 6. left alt keyup
|
|
const size_t convertedSize = 6;
|
|
VERIFY_ARE_EQUAL(convertedSize, events.size());
|
|
|
|
std::deque<KeyEvent> expectedEvents;
|
|
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 });
|
|
|
|
for (size_t i = 0; i < events.size(); ++i)
|
|
{
|
|
const KeyEvent currentKeyEvent = *reinterpret_cast<const KeyEvent* const>(events[i].get());
|
|
VERIFY_ARE_EQUAL(expectedEvents[i], currentKeyEvent, NoThrowString().Format(L"i == %d", i));
|
|
}
|
|
}
|
|
};
|