mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-17 15:36:35 +00:00
wip
This commit is contained in:
208
reference.cpp
Normal file
208
reference.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
void TerminalInput::_initKeyboardMap() noexcept
|
||||
try
|
||||
{
|
||||
auto defineKeyWithUnusedModifiers = [this](const int keyCode, const std::wstring& sequence) {
|
||||
for (auto m = 0; m < 8; m++)
|
||||
_keyMap[VTModifier(m) + keyCode] = sequence;
|
||||
};
|
||||
auto defineKeyWithAltModifier = [this](const int keyCode, const std::wstring& sequence) {
|
||||
_keyMap[keyCode] = sequence;
|
||||
_keyMap[Alt + keyCode] = L"\x1B" + sequence;
|
||||
};
|
||||
auto defineKeypadKey = [this](const int keyCode, const wchar_t* prefix, const wchar_t finalChar) {
|
||||
_keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}"), prefix, finalChar);
|
||||
for (auto m = 1; m < 8; m++)
|
||||
_keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}1;{}{}"), _csi, m + 1, finalChar);
|
||||
};
|
||||
auto defineEditingKey = [this](const int keyCode, const int parm) {
|
||||
_keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}~"), _csi, parm);
|
||||
for (auto m = 1; m < 8; m++)
|
||||
_keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}{};{}~"), _csi, parm, m + 1);
|
||||
};
|
||||
auto defineNumericKey = [this](const int keyCode, const wchar_t finalChar) {
|
||||
_keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}"), _ss3, finalChar);
|
||||
for (auto m = 1; m < 8; m++)
|
||||
_keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}{}{}"), _ss3, m + 1, finalChar);
|
||||
};
|
||||
|
||||
_keyMap.clear();
|
||||
|
||||
// The CSI and SS3 introducers are C1 control codes, which can either be
|
||||
// sent as a single codepoint, or as a two character escape sequence.
|
||||
if (_inputMode.test(Mode::SendC1))
|
||||
{
|
||||
_csi = L"\x9B";
|
||||
_ss3 = L"\x8F";
|
||||
}
|
||||
else
|
||||
{
|
||||
_csi = L"\x1B[";
|
||||
_ss3 = L"\x1BO";
|
||||
}
|
||||
|
||||
// PAUSE doesn't have a VT mapping, but traditionally we've mapped it to ^Z,
|
||||
// regardless of modifiers.
|
||||
defineKeyWithUnusedModifiers(VK_PAUSE, L"\x1A"s);
|
||||
|
||||
// BACKSPACE maps to either DEL or BS, depending on the Backarrow Key mode.
|
||||
// The Ctrl modifier inverts the active mode, swapping BS and DEL (this is
|
||||
// not standard, but a modern terminal convention). The Alt modifier adds
|
||||
// an ESC prefix (also not standard).
|
||||
const auto backSequence = _inputMode.test(Mode::BackarrowKey) ? L"\b"s : L"\x7F"s;
|
||||
const auto ctrlBackSequence = _inputMode.test(Mode::BackarrowKey) ? L"\x7F"s : L"\b"s;
|
||||
defineKeyWithAltModifier(VK_BACK, backSequence);
|
||||
defineKeyWithAltModifier(Ctrl + VK_BACK, ctrlBackSequence);
|
||||
defineKeyWithAltModifier(Shift + VK_BACK, backSequence);
|
||||
defineKeyWithAltModifier(Ctrl + Shift + VK_BACK, ctrlBackSequence);
|
||||
|
||||
// TAB maps to HT, and Shift+TAB to CBT. The Ctrl modifier has no effect.
|
||||
// The Alt modifier adds an ESC prefix, although in practice all the Alt
|
||||
// mappings are likely to be system hotkeys.
|
||||
const auto shiftTabSequence = fmt::format(FMT_COMPILE(L"{}Z"), _csi);
|
||||
defineKeyWithAltModifier(VK_TAB, L"\t"s);
|
||||
defineKeyWithAltModifier(Ctrl + VK_TAB, L"\t"s);
|
||||
defineKeyWithAltModifier(Shift + VK_TAB, shiftTabSequence);
|
||||
defineKeyWithAltModifier(Ctrl + Shift + VK_TAB, shiftTabSequence);
|
||||
|
||||
// RETURN maps to either CR or CR LF, depending on the Line Feed mode. With
|
||||
// a Ctrl modifier it maps to LF, because that's the expected behavior for
|
||||
// most PC keyboard layouts. The Alt modifier adds an ESC prefix.
|
||||
const auto returnSequence = _inputMode.test(Mode::LineFeed) ? L"\r\n"s : L"\r"s;
|
||||
defineKeyWithAltModifier(VK_RETURN, returnSequence);
|
||||
defineKeyWithAltModifier(Shift + VK_RETURN, returnSequence);
|
||||
defineKeyWithAltModifier(Ctrl + VK_RETURN, L"\n"s);
|
||||
defineKeyWithAltModifier(Ctrl + Shift + VK_RETURN, L"\n"s);
|
||||
|
||||
// The keypad RETURN key works the same way, except when Keypad mode is
|
||||
// enabled, but that's handled below with the other keypad keys.
|
||||
defineKeyWithAltModifier(Enhanced + VK_RETURN, returnSequence);
|
||||
defineKeyWithAltModifier(Shift + Enhanced + VK_RETURN, returnSequence);
|
||||
defineKeyWithAltModifier(Ctrl + Enhanced + VK_RETURN, L"\n"s);
|
||||
defineKeyWithAltModifier(Ctrl + Shift + Enhanced + VK_RETURN, L"\n"s);
|
||||
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
// F1 to F4 map to the VT keypad function keys, which are SS3 sequences.
|
||||
// When combined with a modifier, we use CSI sequences with the modifier
|
||||
// embedded as a parameter (not standard - a modern terminal extension).
|
||||
defineKeypadKey(VK_F1, _ss3, L'P');
|
||||
defineKeypadKey(VK_F2, _ss3, L'Q');
|
||||
defineKeypadKey(VK_F3, _ss3, L'R');
|
||||
defineKeypadKey(VK_F4, _ss3, L'S');
|
||||
|
||||
// F5 through F20 map to the top row VT function keys. They use standard
|
||||
// DECFNK sequences with the modifier embedded as a parameter. The first
|
||||
// five function keys on a VT terminal are typically local functions, so
|
||||
// there's not much need to support mappings for them.
|
||||
for (auto vk = VK_F5; vk <= VK_F20; vk++)
|
||||
{
|
||||
static constexpr std::array<uint8_t, 16> parameters = { 15, 17, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 31, 32, 33, 34 };
|
||||
const auto parm = parameters.at(static_cast<size_t>(vk) - VK_F5);
|
||||
defineEditingKey(vk, parm);
|
||||
}
|
||||
|
||||
// Cursor keys follow a similar pattern to the VT keypad function keys,
|
||||
// although they only use an SS3 prefix when the Cursor Key mode is set.
|
||||
// When combined with a modifier, they'll use CSI sequences with the
|
||||
// modifier embedded as a parameter (again not standard).
|
||||
const auto ckIntroducer = _inputMode.test(Mode::CursorKey) ? _ss3 : _csi;
|
||||
defineKeypadKey(VK_UP, ckIntroducer, L'A');
|
||||
defineKeypadKey(VK_DOWN, ckIntroducer, L'B');
|
||||
defineKeypadKey(VK_RIGHT, ckIntroducer, L'C');
|
||||
defineKeypadKey(VK_LEFT, ckIntroducer, L'D');
|
||||
defineKeypadKey(VK_CLEAR, ckIntroducer, L'E');
|
||||
defineKeypadKey(VK_HOME, ckIntroducer, L'H');
|
||||
defineKeypadKey(VK_END, ckIntroducer, L'F');
|
||||
|
||||
// Editing keys follow the same pattern as the top row VT function
|
||||
// keys, using standard DECFNK sequences with the modifier embedded.
|
||||
defineEditingKey(VK_INSERT, 2);
|
||||
defineEditingKey(VK_DELETE, 3);
|
||||
defineEditingKey(VK_PRIOR, 5);
|
||||
defineEditingKey(VK_NEXT, 6);
|
||||
|
||||
// Keypad keys depend on the Keypad mode. When reset, they transmit
|
||||
// the ASCII character assigned by the keyboard layout, but when set
|
||||
// they transmit SS3 escape sequences. When used with a modifier, the
|
||||
// modifier is embedded as a parameter value (not standard).
|
||||
if (Feature_KeypadModeEnabled::IsEnabled() && _inputMode.test(Mode::Keypad))
|
||||
{
|
||||
defineNumericKey(VK_MULTIPLY, L'j');
|
||||
defineNumericKey(VK_ADD, L'k');
|
||||
defineNumericKey(VK_SEPARATOR, L'l');
|
||||
defineNumericKey(VK_SUBTRACT, L'm');
|
||||
defineNumericKey(VK_DECIMAL, L'n');
|
||||
defineNumericKey(VK_DIVIDE, L'o');
|
||||
|
||||
defineNumericKey(VK_NUMPAD0, L'p');
|
||||
defineNumericKey(VK_NUMPAD1, L'q');
|
||||
defineNumericKey(VK_NUMPAD2, L'r');
|
||||
defineNumericKey(VK_NUMPAD3, L's');
|
||||
defineNumericKey(VK_NUMPAD4, L't');
|
||||
defineNumericKey(VK_NUMPAD5, L'u');
|
||||
defineNumericKey(VK_NUMPAD6, L'v');
|
||||
defineNumericKey(VK_NUMPAD7, L'w');
|
||||
defineNumericKey(VK_NUMPAD8, L'x');
|
||||
defineNumericKey(VK_NUMPAD9, L'y');
|
||||
|
||||
defineNumericKey(Enhanced + VK_RETURN, L'M');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// In VT52 mode, the sequences tend to use the same final character as
|
||||
// their ANSI counterparts, but with a simple ESC prefix. The modifier
|
||||
// keys have no effect.
|
||||
|
||||
// VT52 only support PF1 through PF4 function keys.
|
||||
defineKeyWithUnusedModifiers(VK_F1, L"\033P"s);
|
||||
defineKeyWithUnusedModifiers(VK_F2, L"\033Q"s);
|
||||
defineKeyWithUnusedModifiers(VK_F3, L"\033R"s);
|
||||
defineKeyWithUnusedModifiers(VK_F4, L"\033S"s);
|
||||
|
||||
// But terminals with application functions keys would
|
||||
// map some of them as controls keys in VT52 mode.
|
||||
defineKeyWithUnusedModifiers(VK_F11, L"\033"s);
|
||||
defineKeyWithUnusedModifiers(VK_F12, L"\b"s);
|
||||
defineKeyWithUnusedModifiers(VK_F13, L"\n"s);
|
||||
|
||||
// Cursor keys use the same finals as the ANSI sequences.
|
||||
defineKeyWithUnusedModifiers(VK_UP, L"\033A"s);
|
||||
defineKeyWithUnusedModifiers(VK_DOWN, L"\033B"s);
|
||||
defineKeyWithUnusedModifiers(VK_RIGHT, L"\033C"s);
|
||||
defineKeyWithUnusedModifiers(VK_LEFT, L"\033D"s);
|
||||
defineKeyWithUnusedModifiers(VK_CLEAR, L"\033E"s);
|
||||
defineKeyWithUnusedModifiers(VK_HOME, L"\033H"s);
|
||||
defineKeyWithUnusedModifiers(VK_END, L"\033F"s);
|
||||
|
||||
// Keypad keys also depend on Keypad mode, the same as ANSI mappings,
|
||||
// but the sequences use an ESC ? prefix instead of SS3.
|
||||
if (Feature_KeypadModeEnabled::IsEnabled() && _inputMode.test(Mode::Keypad))
|
||||
{
|
||||
defineKeyWithUnusedModifiers(VK_MULTIPLY, L"\033?j"s);
|
||||
defineKeyWithUnusedModifiers(VK_ADD, L"\033?k"s);
|
||||
defineKeyWithUnusedModifiers(VK_SEPARATOR, L"\033?l"s);
|
||||
defineKeyWithUnusedModifiers(VK_SUBTRACT, L"\033?m"s);
|
||||
defineKeyWithUnusedModifiers(VK_DECIMAL, L"\033?n"s);
|
||||
defineKeyWithUnusedModifiers(VK_DIVIDE, L"\033?o"s);
|
||||
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD0, L"\033?p"s);
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD1, L"\033?q"s);
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD2, L"\033?r"s);
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD3, L"\033?s"s);
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD4, L"\033?t"s);
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD5, L"\033?u"s);
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD6, L"\033?v"s);
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD7, L"\033?w"s);
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD8, L"\033?x"s);
|
||||
defineKeyWithUnusedModifiers(VK_NUMPAD9, L"\033?y"s);
|
||||
|
||||
defineKeyWithUnusedModifiers(Enhanced + VK_RETURN, L"\033?M"s);
|
||||
}
|
||||
}
|
||||
|
||||
_focusInSequence = _csi + L"I"s;
|
||||
_focusOutSequence = _csi + L"O"s;
|
||||
}
|
||||
CATCH_LOG()
|
||||
@@ -600,96 +600,12 @@ namespace
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#pragma region TAEF hookup for the test case array above
|
||||
struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataRow>
|
||||
{
|
||||
HRESULT RuntimeClassInitialize(const size_t index)
|
||||
{
|
||||
_index = index;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
const auto indexString{ wil::str_printf<std::wstring>(L"%zu", _index) };
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto indexBstr{ wil::make_bstr(indexString.c_str()) };
|
||||
(void)SafeArrayPutElement(safeArray, &index, indexBstr.release());
|
||||
*ppData = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
|
||||
{
|
||||
*ppMetadataNames = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
*ppData = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetName(BSTR* ppszRowName) override
|
||||
{
|
||||
*ppszRowName = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index;
|
||||
};
|
||||
|
||||
struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataSource>
|
||||
{
|
||||
STDMETHODIMP Advance(IDataRow** ppDataRow) override
|
||||
{
|
||||
if (_index < std::extent_v<decltype(testCases)>)
|
||||
{
|
||||
Microsoft::WRL::MakeAndInitialize<ArrayIndexTaefAdapterRow>(ppDataRow, _index++);
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppDataRow = nullptr;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP Reset() override
|
||||
{
|
||||
_index = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
|
||||
{
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto dataNameBstr{ wil::make_bstr(L"index") };
|
||||
(void)SafeArrayPutElement(safeArray, &index, dataNameBstr.release());
|
||||
*names = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
|
||||
{
|
||||
*type = nullptr;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index{ 0 };
|
||||
};
|
||||
#pragma endregion
|
||||
}
|
||||
|
||||
extern "C" HRESULT __declspec(dllexport) __cdecl ReflowTestDataSource(IDataSource** ppDataSource, void*)
|
||||
{
|
||||
auto source{ Microsoft::WRL::Make<ArrayIndexTaefAdapterSource>() };
|
||||
return source.CopyTo(ppDataSource);
|
||||
*ppDataSource = new ArrayIndexTaefAdapterSource>(std::size(testCases));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
class ReflowTests
|
||||
|
||||
@@ -19,6 +19,8 @@ Revision History:
|
||||
|
||||
#include <til/bit.h>
|
||||
|
||||
#include <IDataSource.h>
|
||||
|
||||
// Helper for declaring a variable to store a TEST_METHOD_PROPERTY and get it's value from the test metadata
|
||||
#define INIT_TEST_PROPERTY(type, identifier, description) \
|
||||
type identifier; \
|
||||
@@ -45,6 +47,178 @@ Revision History:
|
||||
|
||||
namespace WEX::TestExecution
|
||||
{
|
||||
struct ArrayIndexTaefAdapterRow : IDataRow
|
||||
{
|
||||
ArrayIndexTaefAdapterRow(size_t index) :
|
||||
_index(index) {}
|
||||
|
||||
// IUnknown
|
||||
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override
|
||||
{
|
||||
if (!ppvObject)
|
||||
{
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == __uuidof(IUnknown))
|
||||
{
|
||||
AddRef();
|
||||
*ppvObject = static_cast<IUnknown*>(this);
|
||||
return S_OK;
|
||||
}
|
||||
else if (riid == __uuidof(IDataRow))
|
||||
{
|
||||
*ppvObject = static_cast<IDataRow*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppvObject = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef() override
|
||||
{
|
||||
return _refCount.fetch_add(1) + 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Release() override
|
||||
{
|
||||
const auto count = _refCount.fetch_sub(1) - 1;
|
||||
if (count == 0)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// IDataRow
|
||||
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
wchar_t buf[16];
|
||||
swprintf_s(buf, L"%zu", _index);
|
||||
|
||||
LONG idx = 0;
|
||||
const auto array = SafeArrayCreateVector(VT_BSTR, 0, 1);
|
||||
SafeArrayPutElement(array, &idx, SysAllocString(buf));
|
||||
*ppData = array;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
|
||||
{
|
||||
*ppMetadataNames = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
*ppData = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetName(BSTR* ppszRowName) override
|
||||
{
|
||||
*ppszRowName = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<ULONG> _refCount{ 1 };
|
||||
size_t _index = 0;
|
||||
};
|
||||
|
||||
struct ArrayIndexTaefAdapterSource : IDataSource
|
||||
{
|
||||
explicit ArrayIndexTaefAdapterSource(size_t count) :
|
||||
_count{ count } {}
|
||||
|
||||
// IUnknown
|
||||
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override
|
||||
{
|
||||
if (!ppvObject)
|
||||
{
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == __uuidof(IUnknown))
|
||||
{
|
||||
AddRef();
|
||||
*ppvObject = static_cast<IUnknown*>(this);
|
||||
return S_OK;
|
||||
}
|
||||
else if (riid == __uuidof(IDataSource))
|
||||
{
|
||||
*ppvObject = static_cast<IDataSource*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppvObject = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef() override
|
||||
{
|
||||
return _refCount.fetch_add(1) + 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Release() override
|
||||
{
|
||||
const auto count = _refCount.fetch_sub(1) - 1;
|
||||
if (count == 0)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// IDataSource
|
||||
STDMETHODIMP Advance(IDataRow** ppDataRow) override
|
||||
{
|
||||
if (_index < _count)
|
||||
{
|
||||
*ppDataRow = static_cast<IDataRow*>(new ArrayIndexTaefAdapterRow(_index++));
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppDataRow = nullptr;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP Reset() override
|
||||
{
|
||||
_index = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
|
||||
{
|
||||
LONG idx = 0;
|
||||
const auto array = SafeArrayCreateVector(VT_BSTR, 0, 1);
|
||||
SafeArrayPutElement(array, &idx, SysAllocString(L"index"));
|
||||
*names = array;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
|
||||
{
|
||||
*type = nullptr;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<ULONG> _refCount{ 1 };
|
||||
size_t _count = 0;
|
||||
size_t _index = 0;
|
||||
};
|
||||
|
||||
// Compare two floats using a ULP (unit last place) tolerance of up to 4.
|
||||
// Allows you to compare two floats that are almost equal.
|
||||
// Think of: 0.200000000000000 vs. 0.200000000000001.
|
||||
|
||||
@@ -109,97 +109,10 @@ static constexpr til::point point_offset_by_line(const til::point start, const t
|
||||
// IMPORTANT: reference this _after_ defining point_offset_by_XXX. We need it for some definitions
|
||||
#include "GeneratedUiaTextRangeMovementTests.g.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
#pragma region TAEF hookup for the test case array above
|
||||
struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataRow>
|
||||
{
|
||||
HRESULT RuntimeClassInitialize(const size_t index)
|
||||
{
|
||||
_index = index;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
const auto indexString{ wil::str_printf<std::wstring>(L"%zu", _index) };
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto indexBstr{ wil::make_bstr(indexString.c_str()) };
|
||||
(void)SafeArrayPutElement(safeArray, &index, indexBstr.release());
|
||||
*ppData = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
|
||||
{
|
||||
*ppMetadataNames = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
*ppData = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetName(BSTR* ppszRowName) override
|
||||
{
|
||||
*ppszRowName = wil::make_bstr(s_movementTests[_index].name.data()).release();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index;
|
||||
};
|
||||
|
||||
struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataSource>
|
||||
{
|
||||
STDMETHODIMP Advance(IDataRow** ppDataRow) override
|
||||
{
|
||||
if (_index < s_movementTests.size())
|
||||
{
|
||||
Microsoft::WRL::MakeAndInitialize<ArrayIndexTaefAdapterRow>(ppDataRow, _index++);
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppDataRow = nullptr;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP Reset() override
|
||||
{
|
||||
_index = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
|
||||
{
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto dataNameBstr{ wil::make_bstr(L"index") };
|
||||
(void)SafeArrayPutElement(safeArray, &index, dataNameBstr.release());
|
||||
*names = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
|
||||
{
|
||||
*type = nullptr;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index{ 0 };
|
||||
};
|
||||
#pragma endregion
|
||||
}
|
||||
|
||||
extern "C" HRESULT __declspec(dllexport) __cdecl GeneratedMovementTestDataSource(IDataSource** ppDataSource, void*)
|
||||
{
|
||||
auto source{ Microsoft::WRL::Make<ArrayIndexTaefAdapterSource>() };
|
||||
return source.CopyTo(ppDataSource);
|
||||
*ppDataSource = new ArrayIndexTaefAdapterSource>(std::size(testCases));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// UiaTextRange takes an object that implements
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
virtual void DeleteColumn(const VTInt distance) = 0; // DECDC
|
||||
virtual void SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM
|
||||
virtual void SetAnsiMode(const bool ansiMode) = 0; // DECANM
|
||||
virtual void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) = 0; // CSI = flags ; mode u
|
||||
virtual void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept = 0; // CSI = flags ; mode u
|
||||
virtual void QueryKittyKeyboardProtocol() = 0; // CSI ? u
|
||||
virtual void PushKittyKeyboardProtocol(const VTParameter flags) = 0; // CSI > flags u
|
||||
virtual void PopKittyKeyboardProtocol(const VTParameter count) = 0; // CSI < count u
|
||||
|
||||
@@ -2055,9 +2055,9 @@ void AdaptDispatch::SetKeypadMode(const bool fApplicationMode) noexcept
|
||||
}
|
||||
|
||||
// CSI = flags ; mode u - Sets kitty keyboard protocol flags
|
||||
void AdaptDispatch::SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode)
|
||||
void AdaptDispatch::SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept
|
||||
{
|
||||
const auto kittyFlags = static_cast<uint8_t>(flags.value_or(0));
|
||||
const auto kittyFlags = gsl::narrow_cast<uint8_t>(flags.value_or(0));
|
||||
const auto KittyKeyboardProtocol = static_cast<TerminalInput::KittyKeyboardProtocolMode>(mode.value_or(1));
|
||||
_terminalInput.SetKittyKeyboardProtocol(kittyFlags, KittyKeyboardProtocol);
|
||||
}
|
||||
@@ -2072,7 +2072,7 @@ void AdaptDispatch::QueryKittyKeyboardProtocol()
|
||||
// CSI > flags u - Pushes current kitty keyboard flags onto the stack and sets new flags
|
||||
void AdaptDispatch::PushKittyKeyboardProtocol(const VTParameter flags)
|
||||
{
|
||||
const auto kittyFlags = static_cast<uint8_t>(flags.value_or(0));
|
||||
const auto kittyFlags = gsl::narrow_cast<uint8_t>(flags.value_or(0));
|
||||
_terminalInput.PushKittyFlags(kittyFlags);
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void RequestMode(const DispatchTypes::ModeParams param) override; // DECRQM
|
||||
void SetKeypadMode(const bool applicationMode) noexcept override; // DECKPAM, DECKPNM
|
||||
void SetAnsiMode(const bool ansiMode) override; // DECANM
|
||||
void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) override; // Kitty keyboard protocol CSI = flags ; mode u
|
||||
void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept override; // Kitty keyboard protocol CSI = flags ; mode u
|
||||
void QueryKittyKeyboardProtocol() override; // Kitty keyboard protocol CSI ? u
|
||||
void PushKittyKeyboardProtocol(const VTParameter flags) override; // Kitty keyboard protocol CSI > flags u
|
||||
void PopKittyKeyboardProtocol(const VTParameter count) override; // Kitty keyboard protocol CSI < count u
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
void DeleteColumn(const VTInt /*distance*/) override {} // DECDC
|
||||
void SetKeypadMode(const bool /*applicationMode*/) override {} // DECKPAM, DECKPNM
|
||||
void SetAnsiMode(const bool /*ansiMode*/) override {} // DECANM
|
||||
void SetKittyKeyboardProtocol(const VTParameter /*flags*/, const VTParameter /*mode*/) override {} // CSI = flags ; mode u
|
||||
void SetKittyKeyboardProtocol(const VTParameter /*flags*/, const VTParameter /*mode*/) noexcept override {} // CSI = flags ; mode u
|
||||
void QueryKittyKeyboardProtocol() override {} // CSI ? u
|
||||
void PushKittyKeyboardProtocol(const VTParameter /*flags*/) override {} // CSI > flags u
|
||||
void PopKittyKeyboardProtocol(const VTParameter /*count*/) override {} // CSI < count u
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="adapterTest.cpp" />
|
||||
<ClCompile Include="inputTest.cpp" />
|
||||
<ClCompile Include="kittyKeyboardProtocol.cpp" />
|
||||
<ClCompile Include="MouseInputTest.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
@@ -71,4 +72,4 @@
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<Import Project="$(SolutionDir)src\common.build.tests.props" />
|
||||
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -27,10 +27,18 @@
|
||||
<ClCompile Include="MouseInputTest.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="kittyKeyboardProtocol.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\precomp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natstepfilter" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
720
src/terminal/adapter/ut_adapter/kittyKeyboardProtocol.cpp
Normal file
720
src/terminal/adapter/ut_adapter/kittyKeyboardProtocol.cpp
Normal file
@@ -0,0 +1,720 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include <WexTestClass.h>
|
||||
#include <consoletaeftemplates.hpp>
|
||||
|
||||
#include "../../input/terminalInput.hpp"
|
||||
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::Common;
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
using KittyKeyboardProtocolFlags = TerminalInput::KittyKeyboardProtocolFlags;
|
||||
using KittyKeyboardProtocolMode = TerminalInput::KittyKeyboardProtocolMode;
|
||||
|
||||
namespace
|
||||
{
|
||||
TerminalInput::OutputType process(TerminalInput& input, bool keyDown, uint16_t vk, uint16_t sc, wchar_t ch, uint32_t state)
|
||||
{
|
||||
INPUT_RECORD record{};
|
||||
record.EventType = KEY_EVENT;
|
||||
record.Event.KeyEvent.bKeyDown = keyDown ? TRUE : FALSE;
|
||||
record.Event.KeyEvent.wRepeatCount = 1;
|
||||
record.Event.KeyEvent.wVirtualKeyCode = vk;
|
||||
record.Event.KeyEvent.wVirtualScanCode = sc;
|
||||
record.Event.KeyEvent.uChar.UnicodeChar = ch;
|
||||
record.Event.KeyEvent.dwControlKeyState = state;
|
||||
return input.HandleKey(record);
|
||||
}
|
||||
|
||||
TerminalInput createInput(uint8_t flags)
|
||||
{
|
||||
TerminalInput input;
|
||||
input.SetKittyKeyboardProtocol(flags, KittyKeyboardProtocolMode::Replace);
|
||||
return input;
|
||||
}
|
||||
|
||||
// Kitty modifier bit values (as transmitted, before adding 1):
|
||||
// shift=1, alt=2, ctrl=4, super=8, hyper=16, meta=32, caps_lock=64, num_lock=128
|
||||
// Transmitted as: 1 + modifiers
|
||||
|
||||
// CSI = "\x1b["
|
||||
|
||||
// Helper macros for common state combinations
|
||||
constexpr auto ALT_PRESSED = LEFT_ALT_PRESSED; // Use left for consistency
|
||||
constexpr auto CTRL_PRESSED = LEFT_CTRL_PRESSED;
|
||||
|
||||
struct TestCase
|
||||
{
|
||||
std::wstring_view name;
|
||||
std::wstring_view expected;
|
||||
uint8_t flags; // KittyKeyboardProtocolFlags
|
||||
bool keyDown;
|
||||
uint16_t vk;
|
||||
uint16_t sc;
|
||||
wchar_t ch;
|
||||
uint32_t state;
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// Test case organization:
|
||||
//
|
||||
// 1. FLAG COMBINATIONS (32 total = 2^5 enhancement flags)
|
||||
// Testing each flag combination with a representative key
|
||||
//
|
||||
// 2. MODIFIER COMBINATIONS
|
||||
// Testing all modifier permutations (shift, alt, ctrl, caps_lock, num_lock)
|
||||
//
|
||||
// 3. SPECIAL KEY BEHAVIORS
|
||||
// - Enter/Tab/Backspace legacy behavior
|
||||
// - Escape key disambiguation
|
||||
// - Keypad keys
|
||||
// - Function keys
|
||||
// - Lock keys
|
||||
// - Modifier keys themselves
|
||||
//
|
||||
// 4. EVENT TYPES
|
||||
// - Press, repeat, release events
|
||||
// - Special handling for Enter/Tab/Backspace release
|
||||
//
|
||||
// 5. ALTERNATE KEYS
|
||||
// - Shifted key codes
|
||||
// - Base layout key codes
|
||||
//
|
||||
// 6. TEXT AS CODEPOINTS
|
||||
// - Text embedded in escape codes
|
||||
// ========================================================================
|
||||
|
||||
constexpr TestCase testCases[] = {
|
||||
// ====================================================================
|
||||
// SECTION 1: Enhancement Flag Combinations (32 combinations)
|
||||
// Using Escape key as representative since it's affected by Disambiguate
|
||||
// ====================================================================
|
||||
|
||||
// flags=0 (0b00000): No enhancements - legacy mode
|
||||
// Escape key in legacy mode: just ESC byte
|
||||
TestCase{ L"Flags=0 (none) Esc key", L"\x1b", 0, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=1 (0b00001): DisambiguateEscapeCodes only
|
||||
// Escape key becomes CSI 27 u
|
||||
TestCase{ L"Flags=1 (Disambiguate) Esc key", L"\x1b[27u", 1, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=2 (0b00010): ReportEventTypes only
|
||||
// No disambiguation, so Esc is still legacy (but with event type tracking internally)
|
||||
TestCase{ L"Flags=2 (EventTypes) Esc key down", L"\x1b", 2, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=3 (0b00011): Disambiguate + EventTypes
|
||||
// Escape key with event type: CSI 27;1:1 u (mod=1, event=press=1)
|
||||
TestCase{ L"Flags=3 (Disambiguate+EventTypes) Esc key press", L"\x1b[27;1:1u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=4 (0b00100): ReportAlternateKeys only
|
||||
// Without Disambiguate, Escape is still legacy
|
||||
TestCase{ L"Flags=4 (AltKeys) Esc key", L"\x1b", 4, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=5 (0b00101): Disambiguate + AltKeys
|
||||
TestCase{ L"Flags=5 (Disambiguate+AltKeys) Esc key", L"\x1b[27u", 5, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=6 (0b00110): EventTypes + AltKeys
|
||||
TestCase{ L"Flags=6 (EventTypes+AltKeys) Esc key", L"\x1b", 6, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=7 (0b00111): Disambiguate + EventTypes + AltKeys
|
||||
TestCase{ L"Flags=7 (Disambiguate+EventTypes+AltKeys) Esc key press", L"\x1b[27;1:1u", 7, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=8 (0b01000): ReportAllKeysAsEscapeCodes only
|
||||
// All keys become CSI u, including Escape
|
||||
TestCase{ L"Flags=8 (AllKeys) Esc key", L"\x1b[27u", 8, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=9 (0b01001): Disambiguate + AllKeys
|
||||
TestCase{ L"Flags=9 (Disambiguate+AllKeys) Esc key", L"\x1b[27u", 9, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=10 (0b01010): EventTypes + AllKeys
|
||||
TestCase{ L"Flags=10 (EventTypes+AllKeys) Esc key press", L"\x1b[27;1:1u", 10, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=11 (0b01011): Disambiguate + EventTypes + AllKeys
|
||||
TestCase{ L"Flags=11 (Disambiguate+EventTypes+AllKeys) Esc key press", L"\x1b[27;1:1u", 11, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=12 (0b01100): AltKeys + AllKeys
|
||||
TestCase{ L"Flags=12 (AltKeys+AllKeys) Esc key", L"\x1b[27u", 12, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=13 (0b01101): Disambiguate + AltKeys + AllKeys
|
||||
TestCase{ L"Flags=13 (Disambiguate+AltKeys+AllKeys) Esc key", L"\x1b[27u", 13, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=14 (0b01110): EventTypes + AltKeys + AllKeys
|
||||
TestCase{ L"Flags=14 (EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27;1:1u", 14, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=15 (0b01111): Disambiguate + EventTypes + AltKeys + AllKeys
|
||||
TestCase{ L"Flags=15 (Disambiguate+EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27;1:1u", 15, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=16 (0b10000): ReportAssociatedText only (meaningless without AllKeys)
|
||||
TestCase{ L"Flags=16 (AssocText) Esc key", L"\x1b", 16, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=17 (0b10001): Disambiguate + AssocText
|
||||
TestCase{ L"Flags=17 (Disambiguate+AssocText) Esc key", L"\x1b[27u", 17, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=18 (0b10010): EventTypes + AssocText
|
||||
TestCase{ L"Flags=18 (EventTypes+AssocText) Esc key", L"\x1b", 18, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=19 (0b10011): Disambiguate + EventTypes + AssocText
|
||||
TestCase{ L"Flags=19 (Disambiguate+EventTypes+AssocText) Esc key press", L"\x1b[27;1:1u", 19, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=20 (0b10100): AltKeys + AssocText
|
||||
TestCase{ L"Flags=20 (AltKeys+AssocText) Esc key", L"\x1b", 20, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=21 (0b10101): Disambiguate + AltKeys + AssocText
|
||||
TestCase{ L"Flags=21 (Disambiguate+AltKeys+AssocText) Esc key", L"\x1b[27u", 21, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=22 (0b10110): EventTypes + AltKeys + AssocText
|
||||
TestCase{ L"Flags=22 (EventTypes+AltKeys+AssocText) Esc key", L"\x1b", 22, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=23 (0b10111): Disambiguate + EventTypes + AltKeys + AssocText
|
||||
TestCase{ L"Flags=23 (Disambiguate+EventTypes+AltKeys+AssocText) Esc key press", L"\x1b[27;1:1u", 23, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=24 (0b11000): AllKeys + AssocText
|
||||
// 'a' key with text reporting: CSI 97;;97 u
|
||||
TestCase{ L"Flags=24 (AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 24, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=25 (0b11001): Disambiguate + AllKeys + AssocText
|
||||
TestCase{ L"Flags=25 (Disambiguate+AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 25, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=26 (0b11010): EventTypes + AllKeys + AssocText
|
||||
TestCase{ L"Flags=26 (EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;1:1;97u", 26, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=27 (0b11011): Disambiguate + EventTypes + AllKeys + AssocText
|
||||
TestCase{ L"Flags=27 (Disambiguate+EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;1:1;97u", 27, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=28 (0b11100): AltKeys + AllKeys + AssocText
|
||||
TestCase{ L"Flags=28 (AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 28, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=29 (0b11101): Disambiguate + AltKeys + AllKeys + AssocText
|
||||
TestCase{ L"Flags=29 (Disambiguate+AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 29, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=30 (0b11110): EventTypes + AltKeys + AllKeys + AssocText
|
||||
TestCase{ L"Flags=30 (EventTypes+AltKeys+AllKeys+AssocText) 'a' key press", L"\x1b[97;1:1;97u", 30, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=31 (0b11111): All flags enabled
|
||||
TestCase{ L"Flags=31 (all) 'a' key press", L"\x1b[97;1:1;97u", 31, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 2: Modifier Combinations with Disambiguate (flag=1)
|
||||
// Testing all modifier permutations with 'a' key
|
||||
// Kitty modifier encoding: shift=1, alt=2, ctrl=4, caps_lock=64, num_lock=128
|
||||
// Transmitted value = 1 + modifiers
|
||||
// ====================================================================
|
||||
|
||||
// Alt+'a' -> CSI 97;3 u (mod=1+2=3)
|
||||
TestCase{ L"Disambiguate: Alt+a", L"\x1b[97;3u", 1, true, 'A', 0x1E, L'a', ALT_PRESSED },
|
||||
|
||||
// Ctrl+'a' -> CSI 97;5 u (mod=1+4=5)
|
||||
TestCase{ L"Disambiguate: Ctrl+a", L"\x1b[97;5u", 1, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
|
||||
|
||||
// Ctrl+Alt+'a' -> CSI 97;7 u (mod=1+2+4=7)
|
||||
TestCase{ L"Disambiguate: Ctrl+Alt+a", L"\x1b[97;7u", 1, true, 'A', 0x1E, L'\x01', CTRL_PRESSED | ALT_PRESSED },
|
||||
|
||||
// Shift+Alt+'a' -> CSI 97;4 u (mod=1+1+2=4)
|
||||
TestCase{ L"Disambiguate: Shift+Alt+a", L"\x1b[97;4u", 1, true, 'A', 0x1E, L'A', SHIFT_PRESSED | ALT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 3: Modifier combinations with AllKeys (flag=8)
|
||||
// All keys produce CSI u, lock keys are reported
|
||||
// ====================================================================
|
||||
|
||||
// No modifiers: 'a' -> CSI 97 u
|
||||
TestCase{ L"AllKeys: 'a' no mods", L"\x1b[97u", 8, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// Shift+'a' -> CSI 97;2 u (mod=1+1=2)
|
||||
TestCase{ L"AllKeys: Shift+a", L"\x1b[97;2u", 8, true, 'A', 0x1E, L'A', SHIFT_PRESSED },
|
||||
|
||||
// Alt+'a' -> CSI 97;3 u (mod=1+2=3)
|
||||
TestCase{ L"AllKeys: Alt+a", L"\x1b[97;3u", 8, true, 'A', 0x1E, L'a', ALT_PRESSED },
|
||||
|
||||
// Ctrl+'a' -> CSI 97;5 u (mod=1+4=5)
|
||||
TestCase{ L"AllKeys: Ctrl+a", L"\x1b[97;5u", 8, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
|
||||
|
||||
// Shift+Alt+'a' -> CSI 97;4 u (mod=1+1+2=4)
|
||||
TestCase{ L"AllKeys: Shift+Alt+a", L"\x1b[97;4u", 8, true, 'A', 0x1E, L'A', SHIFT_PRESSED | ALT_PRESSED },
|
||||
|
||||
// Shift+Ctrl+'a' -> CSI 97;6 u (mod=1+1+4=6)
|
||||
TestCase{ L"AllKeys: Shift+Ctrl+a", L"\x1b[97;6u", 8, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | CTRL_PRESSED },
|
||||
|
||||
// Alt+Ctrl+'a' -> CSI 97;7 u (mod=1+2+4=7)
|
||||
TestCase{ L"AllKeys: Alt+Ctrl+a", L"\x1b[97;7u", 8, true, 'A', 0x1E, L'\x01', ALT_PRESSED | CTRL_PRESSED },
|
||||
|
||||
// Shift+Alt+Ctrl+'a' -> CSI 97;8 u (mod=1+1+2+4=8)
|
||||
TestCase{ L"AllKeys: Shift+Alt+Ctrl+a", L"\x1b[97;8u", 8, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED },
|
||||
|
||||
// CapsLock+'a' -> CSI 97;65 u (mod=1+64=65)
|
||||
TestCase{ L"AllKeys: CapsLock+a", L"\x1b[97;65u", 8, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
|
||||
|
||||
// NumLock+'a' -> CSI 97;129 u (mod=1+128=129)
|
||||
TestCase{ L"AllKeys: NumLock+a", L"\x1b[97;129u", 8, true, 'A', 0x1E, L'a', NUMLOCK_ON },
|
||||
|
||||
// CapsLock+NumLock+'a' -> CSI 97;193 u (mod=1+64+128=193)
|
||||
TestCase{ L"AllKeys: CapsLock+NumLock+a", L"\x1b[97;193u", 8, true, 'A', 0x1E, L'A', CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// Shift+CapsLock+'a' -> CSI 97;66 u (mod=1+1+64=66)
|
||||
TestCase{ L"AllKeys: Shift+CapsLock+a", L"\x1b[97;66u", 8, true, 'A', 0x1E, L'a', SHIFT_PRESSED | CAPSLOCK_ON },
|
||||
|
||||
// All modifiers: Shift+Alt+Ctrl+CapsLock+NumLock
|
||||
// mod=1+1+2+4+64+128=200
|
||||
TestCase{ L"AllKeys: all mods", L"\x1b[97;200u", 8, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 4: Enter, Tab, Backspace - Legacy behavior exceptions
|
||||
// Per spec: "The only exceptions are the Enter, Tab and Backspace keys
|
||||
// which still generate the same bytes as in legacy mode"
|
||||
// ====================================================================
|
||||
|
||||
// With Disambiguate only (flag=1), these stay legacy:
|
||||
// (These should return MakeUnhandled(), causing legacy processing)
|
||||
// We'll test that they DON'T produce CSI u output
|
||||
|
||||
// With AllKeys (flag=8), they DO get CSI u encoding:
|
||||
// Enter -> CSI 13 u
|
||||
TestCase{ L"AllKeys: Enter", L"\x1b[13u", 8, true, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
|
||||
// Tab -> CSI 9 u
|
||||
TestCase{ L"AllKeys: Tab", L"\x1b[9u", 8, true, VK_TAB, 0x0F, L'\t', 0 },
|
||||
|
||||
// Backspace -> CSI 127 u
|
||||
TestCase{ L"AllKeys: Backspace", L"\x1b[127u", 8, true, VK_BACK, 0x0E, L'\b', 0 },
|
||||
|
||||
// Enter with Shift -> CSI 13;2 u
|
||||
TestCase{ L"AllKeys: Shift+Enter", L"\x1b[13;2u", 8, true, VK_RETURN, 0x1C, L'\r', SHIFT_PRESSED },
|
||||
|
||||
// Tab with Ctrl -> CSI 9;5 u
|
||||
TestCase{ L"AllKeys: Ctrl+Tab", L"\x1b[9;5u", 8, true, VK_TAB, 0x0F, L'\t', CTRL_PRESSED },
|
||||
|
||||
// Backspace with Alt -> CSI 127;3 u
|
||||
TestCase{ L"AllKeys: Alt+Backspace", L"\x1b[127;3u", 8, true, VK_BACK, 0x0E, L'\b', ALT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 5: Event Types (flag=2)
|
||||
// press=1, repeat=2, release=3
|
||||
// Format: CSI keycode;mod:event u
|
||||
// ====================================================================
|
||||
|
||||
// Key press with Disambiguate+EventTypes (flag=3)
|
||||
TestCase{ L"EventTypes: Esc press", L"\x1b[27;1:1u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// Key release with Disambiguate+EventTypes (flag=3)
|
||||
TestCase{ L"EventTypes: Esc release", L"\x1b[27;1:3u", 3, false, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// Key press with AllKeys+EventTypes (flag=10)
|
||||
TestCase{ L"EventTypes+AllKeys: 'a' press", L"\x1b[97;1:1u", 10, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// Key release with AllKeys+EventTypes (flag=10)
|
||||
TestCase{ L"EventTypes+AllKeys: 'a' release", L"\x1b[97;1:3u", 10, false, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// Enter/Tab/Backspace release - only with AllKeys+EventTypes
|
||||
// Without AllKeys, release events for these are suppressed
|
||||
TestCase{ L"EventTypes+AllKeys: Enter release", L"\x1b[13;1:3u", 10, false, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
TestCase{ L"EventTypes+AllKeys: Tab release", L"\x1b[9;1:3u", 10, false, VK_TAB, 0x0F, L'\t', 0 },
|
||||
TestCase{ L"EventTypes+AllKeys: Backspace release", L"\x1b[127;1:3u", 10, false, VK_BACK, 0x0E, L'\b', 0 },
|
||||
|
||||
// Press with modifier: Shift+Esc -> CSI 27;2:1 u
|
||||
TestCase{ L"EventTypes: Shift+Esc press", L"\x1b[27;2:1u", 3, true, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED },
|
||||
|
||||
// Release with modifier: Shift+Esc -> CSI 27;2:3 u
|
||||
TestCase{ L"EventTypes: Shift+Esc release", L"\x1b[27;2:3u", 3, false, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 6: Keypad Keys
|
||||
// With Disambiguate, keypad keys get CSI u with special codepoints
|
||||
// ====================================================================
|
||||
|
||||
// Keypad 0-9: 57399-57408
|
||||
TestCase{ L"Disambiguate: Numpad0", L"\x1b[57399u", 1, true, VK_NUMPAD0, 0x52, L'0', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad1", L"\x1b[57400u", 1, true, VK_NUMPAD1, 0x4F, L'1', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad5", L"\x1b[57404u", 1, true, VK_NUMPAD5, 0x4C, L'5', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad9", L"\x1b[57408u", 1, true, VK_NUMPAD9, 0x49, L'9', 0 },
|
||||
|
||||
// Keypad operators
|
||||
TestCase{ L"Disambiguate: Numpad Decimal", L"\x1b[57409u", 1, true, VK_DECIMAL, 0x53, L'.', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad Divide", L"\x1b[57410u", 1, true, VK_DIVIDE, 0x35, L'/', ENHANCED_KEY },
|
||||
TestCase{ L"Disambiguate: Numpad Multiply", L"\x1b[57411u", 1, true, VK_MULTIPLY, 0x37, L'*', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad Subtract", L"\x1b[57412u", 1, true, VK_SUBTRACT, 0x4A, L'-', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad Add", L"\x1b[57413u", 1, true, VK_ADD, 0x4E, L'+', 0 },
|
||||
|
||||
// Keypad with modifiers
|
||||
TestCase{ L"Disambiguate: Shift+Numpad5", L"\x1b[57404;2u", 1, true, VK_NUMPAD5, 0x4C, L'5', SHIFT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Ctrl+Numpad0", L"\x1b[57399;5u", 1, true, VK_NUMPAD0, 0x52, L'0', CTRL_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 7: Lock Keys and Modifier Keys (with AllKeys flag=8)
|
||||
// These report their own key codes
|
||||
// ====================================================================
|
||||
|
||||
// CapsLock key itself -> CSI 57358 u
|
||||
TestCase{ L"AllKeys: CapsLock key press", L"\x1b[57358u", 8, true, VK_CAPITAL, 0x3A, 0, 0 },
|
||||
|
||||
// NumLock key itself -> CSI 57360 u
|
||||
TestCase{ L"AllKeys: NumLock key press", L"\x1b[57360u", 8, true, VK_NUMLOCK, 0x45, 0, ENHANCED_KEY },
|
||||
|
||||
// ScrollLock key itself -> CSI 57359 u
|
||||
TestCase{ L"AllKeys: ScrollLock key press", L"\x1b[57359u", 8, true, VK_SCROLL, 0x46, 0, 0 },
|
||||
|
||||
// Left Shift key -> CSI 57441 u (with shift modifier set)
|
||||
TestCase{ L"AllKeys: Left Shift key press", L"\x1b[57441;2u", 8, true, VK_SHIFT, 0x2A, 0, SHIFT_PRESSED },
|
||||
|
||||
// Right Shift key -> CSI 57447 u
|
||||
TestCase{ L"AllKeys: Right Shift key press", L"\x1b[57447;2u", 8, true, VK_SHIFT, 0x36, 0, SHIFT_PRESSED },
|
||||
|
||||
// Left Ctrl key -> CSI 57442 u (with ctrl modifier set)
|
||||
TestCase{ L"AllKeys: Left Ctrl key press", L"\x1b[57442;5u", 8, true, VK_CONTROL, 0x1D, 0, CTRL_PRESSED },
|
||||
|
||||
// Right Ctrl key -> CSI 57448 u
|
||||
TestCase{ L"AllKeys: Right Ctrl key press", L"\x1b[57448;5u", 8, true, VK_CONTROL, 0x1D, 0, CTRL_PRESSED | ENHANCED_KEY },
|
||||
|
||||
// Left Alt key -> CSI 57443 u (with alt modifier set)
|
||||
TestCase{ L"AllKeys: Left Alt key press", L"\x1b[57443;3u", 8, true, VK_MENU, 0x38, 0, ALT_PRESSED },
|
||||
|
||||
// Right Alt key -> CSI 57449 u
|
||||
TestCase{ L"AllKeys: Right Alt key press", L"\x1b[57449;3u", 8, true, VK_MENU, 0x38, 0, RIGHT_ALT_PRESSED | ENHANCED_KEY },
|
||||
|
||||
// Left Windows key -> CSI 57444 u (super modifier not available in Win32)
|
||||
TestCase{ L"AllKeys: Left Win key press", L"\x1b[57444u", 8, true, VK_LWIN, 0x5B, 0, ENHANCED_KEY },
|
||||
|
||||
// Right Windows key -> CSI 57450 u
|
||||
TestCase{ L"AllKeys: Right Win key press", L"\x1b[57450u", 8, true, VK_RWIN, 0x5C, 0, ENHANCED_KEY },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 8: Special Keys with Disambiguate (flag=1)
|
||||
// ====================================================================
|
||||
|
||||
// Various special keys that get CSI u encoding
|
||||
|
||||
// Pause key -> CSI 57362 u
|
||||
TestCase{ L"AllKeys: Pause key", L"\x1b[57362u", 8, true, VK_PAUSE, 0x45, 0, 0 },
|
||||
|
||||
// PrintScreen key -> CSI 57361 u
|
||||
TestCase{ L"AllKeys: PrintScreen key", L"\x1b[57361u", 8, true, VK_SNAPSHOT, 0x37, 0, ENHANCED_KEY },
|
||||
|
||||
// Menu/Apps key -> CSI 57363 u
|
||||
TestCase{ L"AllKeys: Menu key", L"\x1b[57363u", 8, true, VK_APPS, 0x5D, 0, ENHANCED_KEY },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 9: Legacy text keys with Disambiguate (flag=1)
|
||||
// Per spec: "the keys a-z 0-9 ` - = [ ] \ ; ' , . / with modifiers
|
||||
// alt, ctrl, ctrl+alt, shift+alt" get CSI u encoding
|
||||
// ====================================================================
|
||||
|
||||
// Test each punctuation key with Alt
|
||||
TestCase{ L"Disambiguate: Alt+`", L"\x1b[96;3u", 1, true, VK_OEM_3, 0x29, L'`', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+-", L"\x1b[45;3u", 1, true, VK_OEM_MINUS, 0x0C, L'-', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+=", L"\x1b[61;3u", 1, true, VK_OEM_PLUS, 0x0D, L'=', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+[", L"\x1b[91;3u", 1, true, VK_OEM_4, 0x1A, L'[', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+]", L"\x1b[93;3u", 1, true, VK_OEM_6, 0x1B, L']', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+\\", L"\x1b[92;3u", 1, true, VK_OEM_5, 0x2B, L'\\', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+;", L"\x1b[59;3u", 1, true, VK_OEM_1, 0x27, L';', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+'", L"\x1b[39;3u", 1, true, VK_OEM_7, 0x28, L'\'', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+,", L"\x1b[44;3u", 1, true, VK_OEM_COMMA, 0x33, L',', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+.", L"\x1b[46;3u", 1, true, VK_OEM_PERIOD, 0x34, L'.', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+/", L"\x1b[47;3u", 1, true, VK_OEM_2, 0x35, L'/', ALT_PRESSED },
|
||||
|
||||
// Test numbers with Ctrl
|
||||
TestCase{ L"Disambiguate: Ctrl+0", L"\x1b[48;5u", 1, true, '0', 0x0B, L'0', CTRL_PRESSED },
|
||||
TestCase{ L"Disambiguate: Ctrl+1", L"\x1b[49;5u", 1, true, '1', 0x02, L'1', CTRL_PRESSED },
|
||||
TestCase{ L"Disambiguate: Ctrl+9", L"\x1b[57;5u", 1, true, '9', 0x0A, L'9', CTRL_PRESSED },
|
||||
|
||||
// Test letters with Ctrl+Alt
|
||||
TestCase{ L"Disambiguate: Ctrl+Alt+a", L"\x1b[97;7u", 1, true, 'A', 0x1E, L'\x01', CTRL_PRESSED | ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Ctrl+Alt+z", L"\x1b[122;7u", 1, true, 'Z', 0x2C, L'\x1A', CTRL_PRESSED | ALT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 10: Navigation keys as keypad (without ENHANCED_KEY)
|
||||
// When ENHANCED_KEY is not set, navigation keys are from the keypad
|
||||
// ====================================================================
|
||||
|
||||
// Home without ENHANCED_KEY -> KP_HOME (57423)
|
||||
TestCase{ L"AllKeys: Keypad Home", L"\x1b[57423u", 8, true, VK_HOME, 0x47, 0, 0 },
|
||||
|
||||
// End without ENHANCED_KEY -> KP_END (57424)
|
||||
TestCase{ L"AllKeys: Keypad End", L"\x1b[57424u", 8, true, VK_END, 0x4F, 0, 0 },
|
||||
|
||||
// Insert without ENHANCED_KEY -> KP_INSERT (57425)
|
||||
TestCase{ L"AllKeys: Keypad Insert", L"\x1b[57425u", 8, true, VK_INSERT, 0x52, 0, 0 },
|
||||
|
||||
// Delete without ENHANCED_KEY -> KP_DELETE (57426)
|
||||
TestCase{ L"AllKeys: Keypad Delete", L"\x1b[57426u", 8, true, VK_DELETE, 0x53, 0, 0 },
|
||||
|
||||
// PageUp without ENHANCED_KEY -> KP_PAGE_UP (57421)
|
||||
TestCase{ L"AllKeys: Keypad PageUp", L"\x1b[57421u", 8, true, VK_PRIOR, 0x49, 0, 0 },
|
||||
|
||||
// PageDown without ENHANCED_KEY -> KP_PAGE_DOWN (57422)
|
||||
TestCase{ L"AllKeys: Keypad PageDown", L"\x1b[57422u", 8, true, VK_NEXT, 0x51, 0, 0 },
|
||||
|
||||
// Arrows without ENHANCED_KEY
|
||||
TestCase{ L"AllKeys: Keypad Up", L"\x1b[57419u", 8, true, VK_UP, 0x48, 0, 0 },
|
||||
TestCase{ L"AllKeys: Keypad Down", L"\x1b[57420u", 8, true, VK_DOWN, 0x50, 0, 0 },
|
||||
TestCase{ L"AllKeys: Keypad Left", L"\x1b[57417u", 8, true, VK_LEFT, 0x4B, 0, 0 },
|
||||
TestCase{ L"AllKeys: Keypad Right", L"\x1b[57418u", 8, true, VK_RIGHT, 0x4D, 0, 0 },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 11: Media Keys
|
||||
// ====================================================================
|
||||
|
||||
TestCase{ L"AllKeys: Media Play/Pause", L"\x1b[57430u", 8, true, VK_MEDIA_PLAY_PAUSE, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Media Stop", L"\x1b[57432u", 8, true, VK_MEDIA_STOP, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Media Next Track", L"\x1b[57435u", 8, true, VK_MEDIA_NEXT_TRACK, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Media Prev Track", L"\x1b[57436u", 8, true, VK_MEDIA_PREV_TRACK, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Volume Down", L"\x1b[57438u", 8, true, VK_VOLUME_DOWN, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Volume Up", L"\x1b[57439u", 8, true, VK_VOLUME_UP, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Volume Mute", L"\x1b[57440u", 8, true, VK_VOLUME_MUTE, 0, 0, 0 },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 12: Function Keys (F13-F24)
|
||||
// F1-F12 use legacy sequences, F13-F24 use CSI u with codes 57376-57387
|
||||
// ====================================================================
|
||||
|
||||
TestCase{ L"AllKeys: F13", L"\x1b[57376u", 8, true, VK_F13, 0x64, 0, 0 },
|
||||
TestCase{ L"AllKeys: F14", L"\x1b[57377u", 8, true, VK_F14, 0x65, 0, 0 },
|
||||
TestCase{ L"AllKeys: F15", L"\x1b[57378u", 8, true, VK_F15, 0x66, 0, 0 },
|
||||
TestCase{ L"AllKeys: F16", L"\x1b[57379u", 8, true, VK_F16, 0x67, 0, 0 },
|
||||
TestCase{ L"AllKeys: F17", L"\x1b[57380u", 8, true, VK_F17, 0x68, 0, 0 },
|
||||
TestCase{ L"AllKeys: F18", L"\x1b[57381u", 8, true, VK_F18, 0x69, 0, 0 },
|
||||
TestCase{ L"AllKeys: F19", L"\x1b[57382u", 8, true, VK_F19, 0x6A, 0, 0 },
|
||||
TestCase{ L"AllKeys: F20", L"\x1b[57383u", 8, true, VK_F20, 0x6B, 0, 0 },
|
||||
TestCase{ L"AllKeys: F21", L"\x1b[57384u", 8, true, VK_F21, 0x6C, 0, 0 },
|
||||
TestCase{ L"AllKeys: F22", L"\x1b[57385u", 8, true, VK_F22, 0x6D, 0, 0 },
|
||||
TestCase{ L"AllKeys: F23", L"\x1b[57386u", 8, true, VK_F23, 0x6E, 0, 0 },
|
||||
TestCase{ L"AllKeys: F24", L"\x1b[57387u", 8, true, VK_F24, 0x76, 0, 0 },
|
||||
|
||||
// F13 with modifiers
|
||||
TestCase{ L"AllKeys: Shift+F13", L"\x1b[57376;2u", 8, true, VK_F13, 0x64, 0, SHIFT_PRESSED },
|
||||
TestCase{ L"AllKeys: Ctrl+F13", L"\x1b[57376;5u", 8, true, VK_F13, 0x64, 0, CTRL_PRESSED },
|
||||
TestCase{ L"AllKeys: Alt+F13", L"\x1b[57376;3u", 8, true, VK_F13, 0x64, 0, ALT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 13: Alternate Keys (ReportAlternateKeys flag = 4)
|
||||
// Format: CSI keycode:shifted-key:base-layout-key ; modifiers u
|
||||
// Shifted key is present only when shift modifier is active
|
||||
// Base layout key is the PC-101 US keyboard equivalent
|
||||
// ====================================================================
|
||||
|
||||
// Shift+a with AltKeys flag: 97:65 (a:A) - shifted key is 'A' (65)
|
||||
// flags = AllKeys(8) + AltKeys(4) = 12
|
||||
TestCase{ L"AltKeys+AllKeys: Shift+a", L"\x1b[97:65;2u", 12, true, 'A', 0x1E, L'A', SHIFT_PRESSED },
|
||||
|
||||
// Shift+1 with AltKeys flag: 49:33 (1:!) - shifted key is '!' (33)
|
||||
TestCase{ L"AltKeys+AllKeys: Shift+1", L"\x1b[49:33;2u", 12, true, '1', 0x02, L'!', SHIFT_PRESSED },
|
||||
|
||||
// Shift+[ with AltKeys flag: 91:123 ([:{) - shifted key is '{' (123)
|
||||
TestCase{ L"AltKeys+AllKeys: Shift+[", L"\x1b[91:123;2u", 12, true, VK_OEM_4, 0x1A, L'{', SHIFT_PRESSED },
|
||||
|
||||
// Without shift, no shifted key is reported
|
||||
// 'a' with AltKeys flag (no shift): 97 only, no alternate keys
|
||||
TestCase{ L"AltKeys+AllKeys: a (no shift)", L"\x1b[97u", 12, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 14: Complex combinations
|
||||
// Testing multiple flags together with various keys and modifiers
|
||||
// ====================================================================
|
||||
|
||||
// AllKeys + EventTypes + CapsLock: 'a' press with CapsLock
|
||||
// mod=1+64=65, event=press=1
|
||||
TestCase{ L"AllKeys+EventTypes: CapsLock+a press", L"\x1b[97;65:1u", 10, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
|
||||
|
||||
// AllKeys + EventTypes + all modifiers: press
|
||||
// mod=1+1+2+4+64+128=200, event=1
|
||||
TestCase{ L"AllKeys+EventTypes: all mods press", L"\x1b[97;200:1u", 10, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// AllKeys + EventTypes + all modifiers: release
|
||||
TestCase{ L"AllKeys+EventTypes: all mods release", L"\x1b[97;200:3u", 10, false, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 15: Text with associated codepoints (flag=24: AllKeys + AssocText)
|
||||
// Format: CSI keycode ; modifiers ; text u
|
||||
// ====================================================================
|
||||
|
||||
// 'A' (shifted) with AssocText: CSI 97;2;65 u
|
||||
TestCase{ L"AllKeys+AssocText: Shift+a", L"\x1b[97;2;65u", 24, true, 'A', 0x1E, L'A', SHIFT_PRESSED },
|
||||
|
||||
// Number with shift (symbol): Shift+1 -> '!'
|
||||
// CSI 49;2;33 u (49='1', 33='!')
|
||||
TestCase{ L"AllKeys+AssocText: Shift+1", L"\x1b[49;2;33u", 24, true, '1', 0x02, L'!', SHIFT_PRESSED },
|
||||
|
||||
// Ctrl+a produces control character (0x01), which should not be in text
|
||||
// Text field should be omitted for control codes
|
||||
TestCase{ L"AllKeys+AssocText: Ctrl+a (no text)", L"\x1b[97;5;1u", 24, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 16: Edge cases
|
||||
// ====================================================================
|
||||
|
||||
// Keypad Enter (ENHANCED_KEY set) -> KP_ENTER (57414)
|
||||
TestCase{ L"AllKeys: Keypad Enter", L"\x1b[57414u", 8, true, VK_RETURN, 0x1C, L'\r', ENHANCED_KEY },
|
||||
|
||||
// Regular Enter vs Keypad Enter distinction
|
||||
TestCase{ L"AllKeys: Regular Enter", L"\x1b[13u", 8, true, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
|
||||
// Escape with all basic modifiers
|
||||
TestCase{ L"AllKeys: Shift+Alt+Ctrl+Esc", L"\x1b[27;8u", 8, true, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED },
|
||||
|
||||
// Tab with Shift (special legacy: CSI Z, but with AllKeys should be CSI 9;2 u)
|
||||
TestCase{ L"AllKeys: Shift+Tab", L"\x1b[9;2u", 8, true, VK_TAB, 0x0F, 0, SHIFT_PRESSED },
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" HRESULT __declspec(dllexport) __cdecl KittyKeyTestDataSource(IDataSource** ppDataSource, void*)
|
||||
{
|
||||
*ppDataSource = new ArrayIndexTaefAdapterSource(std::size(testCases));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
class KittyKeyboardProtocolTests
|
||||
{
|
||||
TEST_CLASS(KittyKeyboardProtocolTests);
|
||||
|
||||
TEST_METHOD(KeyPressTests)
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"DataSource", L"Export:KittyKeyTestDataSource")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
DisableVerifyExceptions disableVerifyExceptions{};
|
||||
SetVerifyOutput verifyOutputScope{ VerifyOutputSettings::LogOnlyFailures };
|
||||
|
||||
size_t i{};
|
||||
TestData::TryGetValue(L"index", i);
|
||||
const auto& tc = testCases[i];
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"[%zu] Test case \"%.*s\"", i, tc.name.size(), tc.name.data()));
|
||||
|
||||
auto input = createInput(tc.flags);
|
||||
const auto expected = TerminalInput::MakeOutput(tc.expected);
|
||||
const auto actual = process(input, tc.keyDown, tc.vk, tc.sc, tc.ch, tc.state);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
// Repeat events require stateful testing - the same key must be pressed twice
|
||||
// without a release in between. This cannot be done with the data-driven approach.
|
||||
TEST_METHOD(KeyRepeatEvents)
|
||||
{
|
||||
Log::Comment(L"Testing key repeat event type (event type = 2)");
|
||||
|
||||
// Use EventTypes flag (2) + AllKeys flag (8) = 10
|
||||
constexpr uint8_t flags = 10;
|
||||
auto input = createInput(flags);
|
||||
|
||||
// First press -> event type 1 (press)
|
||||
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
|
||||
VERIFY_ARE_EQUAL(expected1, result1, L"First press should be event type 1");
|
||||
|
||||
// Second press (same key, no release) -> event type 2 (repeat)
|
||||
auto result2 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected2 = TerminalInput::MakeOutput(L"\x1b[97;1:2u");
|
||||
VERIFY_ARE_EQUAL(expected2, result2, L"Second press should be event type 2 (repeat)");
|
||||
|
||||
// Third press (still same key) -> still event type 2 (repeat)
|
||||
auto result3 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected3 = TerminalInput::MakeOutput(L"\x1b[97;1:2u");
|
||||
VERIFY_ARE_EQUAL(expected3, result3, L"Third press should still be event type 2 (repeat)");
|
||||
|
||||
// Release -> event type 3
|
||||
auto result4 = process(input, false, 'A', 0x1E, L'a', 0);
|
||||
auto expected4 = TerminalInput::MakeOutput(L"\x1b[97;1:3u");
|
||||
VERIFY_ARE_EQUAL(expected4, result4, L"Release should be event type 3");
|
||||
|
||||
// Next press after release -> event type 1 (press) again
|
||||
auto result5 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected5 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
|
||||
VERIFY_ARE_EQUAL(expected5, result5, L"Press after release should be event type 1 again");
|
||||
}
|
||||
|
||||
// Test repeat events with modifiers
|
||||
TEST_METHOD(KeyRepeatEventsWithModifiers)
|
||||
{
|
||||
Log::Comment(L"Testing key repeat with Shift modifier");
|
||||
|
||||
constexpr uint8_t flags = 10; // EventTypes + AllKeys
|
||||
auto input = createInput(flags);
|
||||
|
||||
// First Shift+a press -> event type 1
|
||||
auto result1 = process(input, true, 'A', 0x1E, L'A', SHIFT_PRESSED);
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97;2:1u");
|
||||
VERIFY_ARE_EQUAL(expected1, result1, L"First Shift+a press should be event type 1");
|
||||
|
||||
// Repeat Shift+a -> event type 2
|
||||
auto result2 = process(input, true, 'A', 0x1E, L'A', SHIFT_PRESSED);
|
||||
auto expected2 = TerminalInput::MakeOutput(L"\x1b[97;2:2u");
|
||||
VERIFY_ARE_EQUAL(expected2, result2, L"Repeat Shift+a should be event type 2");
|
||||
}
|
||||
|
||||
// Test that pressing different keys resets repeat detection
|
||||
TEST_METHOD(KeyRepeatResetOnDifferentKey)
|
||||
{
|
||||
Log::Comment(L"Testing that pressing a different key resets repeat detection");
|
||||
|
||||
constexpr uint8_t flags = 10; // EventTypes + AllKeys
|
||||
auto input = createInput(flags);
|
||||
|
||||
// Press 'a'
|
||||
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
|
||||
VERIFY_ARE_EQUAL(expected1, result1, L"First 'a' press should be event type 1");
|
||||
|
||||
// Press 'b' (different key) -> should be press, not repeat
|
||||
auto result2 = process(input, true, 'B', 0x30, L'b', 0);
|
||||
auto expected2 = TerminalInput::MakeOutput(L"\x1b[98;1:1u");
|
||||
VERIFY_ARE_EQUAL(expected2, result2, L"'b' press should be event type 1 (not repeat)");
|
||||
|
||||
// Press 'a' again -> should be press since 'b' was pressed in between
|
||||
auto result3 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected3 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
|
||||
VERIFY_ARE_EQUAL(expected3, result3, L"'a' press after 'b' should be event type 1 (new press)");
|
||||
}
|
||||
|
||||
// Test Enter/Tab/Backspace release suppression without AllKeys
|
||||
TEST_METHOD(EnterTabBackspaceReleaseWithoutAllKeys)
|
||||
{
|
||||
Log::Comment(L"Testing that Enter/Tab/Backspace don't report release without AllKeys flag");
|
||||
|
||||
// Use Disambiguate + EventTypes (flags = 3), but NOT AllKeys
|
||||
constexpr uint8_t flags = 3;
|
||||
auto input = createInput(flags);
|
||||
|
||||
// These keys should NOT produce output for release events
|
||||
// (they return MakeUnhandled for press too with just Disambiguate,
|
||||
// but release should produce _makeNoOutput)
|
||||
|
||||
// Note: With flags=3 (no AllKeys), Enter/Tab/Backspace use legacy encoding
|
||||
// and release events should be suppressed (return no output)
|
||||
}
|
||||
|
||||
// Test that without EventTypes flag, release events produce no output
|
||||
TEST_METHOD(ReleaseEventsWithoutEventTypesFlag)
|
||||
{
|
||||
Log::Comment(L"Testing that release events produce no output without EventTypes flag");
|
||||
|
||||
// Use only AllKeys (flag = 8), NOT EventTypes
|
||||
constexpr uint8_t flags = 8;
|
||||
auto input = createInput(flags);
|
||||
|
||||
// Press should produce output
|
||||
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97u");
|
||||
VERIFY_ARE_EQUAL(expected1, result1, L"Press should produce output");
|
||||
|
||||
// Release should produce no output (empty optional)
|
||||
auto result2 = process(input, false, 'A', 0x1E, L'a', 0);
|
||||
VERIFY_IS_FALSE(result2.has_value(), L"Release without EventTypes flag should produce no output");
|
||||
}
|
||||
|
||||
// Test legacy mode (flags=0) produces MakeUnhandled for regular keys
|
||||
TEST_METHOD(LegacyModePassthrough)
|
||||
{
|
||||
Log::Comment(L"Testing that legacy mode (flags=0) returns MakeUnhandled for regular keys");
|
||||
|
||||
constexpr uint8_t flags = 0;
|
||||
auto input = createInput(flags);
|
||||
|
||||
// Regular key 'a' should return MakeUnhandled (falls through to legacy processing)
|
||||
auto result = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto unhandled = TerminalInput::MakeUnhandled();
|
||||
VERIFY_ARE_EQUAL(unhandled, result, L"Regular key in legacy mode should be unhandled");
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -78,8 +78,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
// Kitty keyboard protocol methods
|
||||
void SetKittyKeyboardProtocol(uint8_t flags, KittyKeyboardProtocolMode mode) noexcept;
|
||||
uint8_t GetKittyFlags() const noexcept;
|
||||
void PushKittyFlags(uint8_t flags) noexcept;
|
||||
void PopKittyFlags(size_t count) noexcept;
|
||||
void PushKittyFlags(uint8_t flags);
|
||||
void PopKittyFlags(size_t count);
|
||||
void ResetKittyKeyboardProtocols() noexcept;
|
||||
|
||||
#pragma region MouseInput
|
||||
@@ -96,6 +96,31 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
uint16_t len;
|
||||
};
|
||||
|
||||
struct KeyEncodingInfo
|
||||
{
|
||||
// If not zero, this value represents the first field in the Kitty
|
||||
// Keyboard Protocol (KKP) CSI u sequence. If the KKP is requested,
|
||||
// this field will be preferred over the following fields.
|
||||
int32_t kittyKeyCode = 0;
|
||||
|
||||
// A non-zero csiFinal value indicates that this key
|
||||
// should be encoded as `CSI $csiParam1 ; $csiFinal`.
|
||||
wchar_t csiFinal = 0;
|
||||
int32_t csiParam1 = 0;
|
||||
|
||||
// A non-zero ss3Final value indicates that this key
|
||||
// should be encoded as `ESC O $ss3Final`.
|
||||
wchar_t ss3Final = 0;
|
||||
|
||||
// Any other encoding ends up as a non-zero plain value.
|
||||
// For instance, the Tab key gets translated to a plain "\t".
|
||||
std::wstring_view plain;
|
||||
|
||||
// If true, and Alt is pressed, an ESC prefix should be added to
|
||||
// the final sequence. This only applies to non-KKP encodings.
|
||||
bool altPrefix = false;
|
||||
};
|
||||
|
||||
// storage location for the leading surrogate of a utf-16 surrogate pair
|
||||
wchar_t _leadingSurrogate = 0;
|
||||
|
||||
@@ -118,19 +143,18 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
std::vector<uint8_t> _kittyMainStack;
|
||||
std::vector<uint8_t> _kittyAltStack;
|
||||
|
||||
const wchar_t* _csi = L"\x1B[";
|
||||
const wchar_t* _ss3 = L"\x1BO";
|
||||
static constexpr std::wstring_view _csi{ L"\x1B[" };
|
||||
static constexpr std::wstring_view _ss3{ L"\x1BO" };
|
||||
|
||||
void _initKeyboardMap() noexcept;
|
||||
DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept;
|
||||
static std::array<byte, 256> _getKeyboardState(WORD virtualKeyCode, DWORD controlKeyState);
|
||||
[[nodiscard]] static wchar_t _makeCtrlChar(wchar_t ch);
|
||||
[[nodiscard]] StringType _makeCharOutput(wchar_t ch);
|
||||
[[nodiscard]] static StringType _makeCharOutput(uint32_t ch);
|
||||
[[nodiscard]] static StringType _makeNoOutput() noexcept;
|
||||
void _escapeOutput(StringType& charSequence, bool altIsPressed) const;
|
||||
[[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key) const;
|
||||
[[nodiscard]] OutputType _makeKittyOutput(const KEY_EVENT_RECORD& key, DWORD controlKeyState);
|
||||
static int32_t _getKittyKeyCode(const KEY_EVENT_RECORD& key, DWORD controlKeyState) noexcept;
|
||||
[[nodiscard]] KeyEncodingInfo _getKeyEncodingInfo(const KEY_EVENT_RECORD& key, DWORD simpleKeyState) const noexcept;
|
||||
std::vector<uint8_t>& _getKittyStack() noexcept;
|
||||
static bool _codepointIsText(uint32_t cp) noexcept;
|
||||
static CodepointBuffer _codepointToBuffer(uint32_t cp) noexcept;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user